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.
 
 
 
 
 
 

815 lines
23 KiB

//=========== Copyright © Valve Corporation, All rights reserved. ============//
//
// Purpose: Mesh simplification routines.
//
//===========================================================================//
#include "mathlib/vector.h"
#include "simplify.h"
#include "tier1/utlpriorityqueue.h"
#include "mathlib/cholesky.h"
#include "tier1/utlhash.h"
#include "tier0/vprof.h"
#include "memdbgon.h"
// quick vprof wrappers
static void Vprof_MarkFrame_IfEnabled()
{
#if VPROF_LEVEL > 0
g_VProfCurrentProfile.MarkFrame();
#endif
}
static void Vprof_Start_IfEnabled()
{
#if VPROF_LEVEL > 0
g_VProfCurrentProfile.Reset();
g_VProfCurrentProfile.ResetPeaks();
g_VProfCurrentProfile.Start();
#endif
}
static void Vprof_Report_IfEnabled()
{
#if VPROF_LEVEL > 0
g_VProfCurrentProfile.Stop();
g_VProfCurrentProfile.OutputReport( VPRT_FULL & ~VPRT_HIERARCHY, NULL );
Msg("VPROF Trace Complete\n");
#endif
}
// This is a shared edge and its corresponding error metric
class CQEMEdge
{
public:
CQEMEdge() { Init(0,0); }
CQEMEdge( int nV0, int nV1 ) { Init(nV0, nV1); }
void Init( int nV0, int nV1 )
{
m_bCollapsed = 0;
m_bNonManifold = 0;
m_flCurrentError = 0.0f;
m_nBestIndex = 0;
m_nReferences = 0;
m_nVert[0] = nV0;
m_nVert[1] = nV1;
}
void InitVerts( int nV0, int nV1 )
{
m_nVert[0] = MIN(nV0, nV1);
m_nVert[1] = MAX(nV0, nV1);
}
inline void MarkCollapsed() { m_bCollapsed = true; }
inline bool IsCollapsed() { return m_bCollapsed; }
void UpdateError( const Vector &v0, const Vector &v1, const CQuadricError *pVertError )
{
m_error = pVertError[m_nVert[0]] + pVertError[m_nVert[1]];
m_flCurrentError = m_error.ComputeError(v0);
float err1 = m_error.ComputeError(v1);
m_nBestIndex = 0;
if ( err1 < m_flCurrentError )
{
m_vOptimal = v1;
m_nBestIndex = 1;
m_flCurrentError = err1;
m_flInterp = 1.0f;
}
else
{
m_vOptimal = v0;
m_flInterp = 0.0f;
}
Vector vTest = m_error.SolveForMinimumError();
float flError = m_error.ComputeError( vTest );
if ( flError < m_flCurrentError )
{
Vector vEdge = v1 - v0;
float flLen = VectorNormalize(vEdge);
float flDist0 = (vTest - v0).Length();
float flDist1 = (vTest - v1).Length();
if ( flDist0 < flLen || flDist1 < flLen )
{
m_vOptimal = vTest;
m_flCurrentError = flError;
Vector vTmp = m_vOptimal - v0;
if ( flLen > 0.0f )
{
m_flInterp = (1.0f / flLen) * DotProduct( vTmp, vEdge );
m_flInterp = clamp( m_flInterp, 0.0f, 1.0f );
}
}
}
m_flCurrentError = MAX(0.0f, m_flCurrentError);
}
int GetVertIndex( int nVert ) { return m_nVert[0] == nVert ? 0 : 1; }
CQuadricError m_error;
Vector m_vOptimal;
float m_flCurrentError;
float m_flInterp;
int m_nVert[2];
int m_nReferences;
short m_nBestIndex;
bool m_bCollapsed;
bool m_bNonManifold;
};
// We maintain a sorted queue of edges to collapse. The queue stores a copy of the error and links back to an edge
// as edges collapse, adjacent edges are updated. Rather than searching the queue for their current errors we simply
// insert additional records. So the copy of the error is convenient for sorting the queue and also determining if
// this is the most up to date record for a given edge
struct edge_queue_entry_t
{
float m_flError;
int m_nEdgeIndex;
};
// This is the sorted queue of edges to collapse. It includes some invalid records
// This implements the comparison function for sorting
class CEdgeQueue : public CUtlPriorityQueue<edge_queue_entry_t>
{
public:
static bool IsLowerPriority( const edge_queue_entry_t &node1, const edge_queue_entry_t &node2 )
{
// edges with higher error are lower priority
return ( node1.m_flError > node2.m_flError ) ? true : false;
}
CEdgeQueue( int nInitSize = 0 ) : CUtlPriorityQueue<edge_queue_entry_t>( 0, nInitSize, IsLowerPriority ) {}
};
// This is the adjacency and error metric storage class for a mesh
// Each vertex stores a list of triangles (v0,v1,v2). v0 is the vertex storing the list, the list stores the other two
struct vertex_triangle_t
{
uint32 nV1;
uint32 nV2;
};
class CVertVisit
{
public:
CUtlVector<vertex_triangle_t> m_triangles;
};
struct edge_hash_t
{
CQEMEdge *m_pSharedEdge;
inline bool operator==( const edge_hash_t& src ) const { return src.m_nV0 == m_nV0 && src.m_nV1 == m_nV1; }
uint32 m_nV0;
uint32 m_nV1;
};
class CUniqueVertexList : public CUtlVectorFixedGrowable<uint32, 32>
{
public:
void AddIfUnique( uint32 nVertex )
{
if ( Find( nVertex ) == -1 )
{
AddToTail( nVertex );
}
}
};
class CMeshVisit
{
public:
void BuildFromMesh( const CMesh &input );
int FindMinErrorEdge();
void CollapseEdge( int nCollapse );
void RemapEdge( int nVertexRemove, int nVertexConnect, int nVertexKeep );
void ComputeVertListError( float flOpenEdgePenalty, float flMinArea, float flMaxArea, const mesh_simplifyweights_t *pWeights );
inline void UpdateEdgeError( CQEMEdge *pEdge )
{
pEdge->UpdateError( GetVertexPosition(pEdge->m_nVert[0]), GetVertexPosition(pEdge->m_nVert[1]), m_errorVert.Base() );
edge_queue_entry_t entry;
entry.m_flError = pEdge->m_flCurrentError;
entry.m_nEdgeIndex = pEdge - m_edgeList.Base();
m_edgeQueue.Insert( entry );
}
void Get1Ring( CUniqueVertexList &list, uint32 nVertex );
int CountSharedVerts( int nVert0, int nVert1 );
bool IsValidCollapse( int nMinEdge );
bool IsValidCollapseVertex( int nVertCheck, int nVertOpposite, const Vector &vReplacePos );
int CountTotalUsedVerts();
UtlHashFastHandle_t FindEdge( int nV0, int nV1 )
{
edge_hash_t tmp;
tmp.m_nV0 = MIN(nV0, nV1);
tmp.m_nV1 = MAX(nV0, nV1);
uint nHashKey = VertHashKey(MIN(nV0, nV1), MAX(nV0, nV1));
tmp.m_pSharedEdge = NULL;
return m_edgeHash.Find( nHashKey, tmp );
}
bool IsOpenEdge( int nV0, int nV1 )
{
UtlHashFastHandle_t edgeHashIndex = FindEdge( nV0, nV1 );
if ( m_edgeHash.InvalidHandle() != edgeHashIndex )
{
return m_edgeHash.Element(edgeHashIndex).m_pSharedEdge->m_nReferences < 2 ? true : false;
}
return false;
}
// Copies out the mesh in its current state
void WriteMeshIndexList( CUtlVector<uint32> &indexOut );
inline float *GetVertex( int nIndex ) { return m_pVertexBase + nIndex * m_nVertexStrideFloats; }
inline Vector &GetVertexPosition(int nIndex) { return *(Vector *)GetVertex(nIndex); }
CUtlVector<CQEMEdge> m_edgeList;
CUtlScalarHash<edge_hash_t> m_edgeHash;
CUtlVector<CQuadricError> m_errorVert;
CUtlVector<CVertVisit> m_vertList;
CEdgeQueue m_edgeQueue;
CUtlVector<float> m_vertData;
float *m_pVertexBase;
int m_nVertexStrideFloats;
int m_nInputVertCount;
int m_nTriangleCount;
int m_nCollapseIndex;
float m_flIntegrationPenalty;
};
// Copies out the mesh in its current state
void CMeshVisit::WriteMeshIndexList( CUtlVector<uint32> &indexOut )
{
uint32 nCurrentVertexCount = m_vertList.Count();
int nTotalTrianglesRemaining = 0;
for ( uint32 i = 0; i < nCurrentVertexCount; i++ )
{
nTotalTrianglesRemaining += m_vertList[i].m_triangles.Count();
}
// each triangle must be referenced 3 times, once at each vertex
Assert( (nTotalTrianglesRemaining % 3) == 0 );
indexOut.SetCount( nTotalTrianglesRemaining );
int nWriteIndex = 0;
for ( uint32 i = 0; i < nCurrentVertexCount; i++ )
{
for ( int j = 0; j < m_vertList[i].m_triangles.Count(); j++ )
{
vertex_triangle_t &tri = m_vertList[i].m_triangles[j];
// only write each triangle once, skip the other two defs.
// do this by only writing if v0 is the min vert index
if ( tri.nV1 < i || tri.nV2 < i )
continue;
indexOut[nWriteIndex++] = i;
indexOut[nWriteIndex++] = tri.nV1;
indexOut[nWriteIndex++] = tri.nV2;
}
}
}
// Remaps one of the verts on an edge
void CMeshVisit::RemapEdge( int nVertexRemove, int nVertexConnect, int nVertexKeep )
{
UtlHashFastHandle_t edgeHashIndex = FindEdge( nVertexRemove, nVertexConnect );
if ( m_edgeHash.InvalidHandle() == edgeHashIndex )
return;
edge_hash_t tmp = m_edgeHash.Element( edgeHashIndex );
CQEMEdge *pEdge = tmp.m_pSharedEdge;
bool bNeedsUpdate = false;
for ( int i = 0; i < 2; i++ )
{
if ( pEdge->m_nVert[i] == nVertexRemove )
{
pEdge->m_nVert[i] = nVertexKeep;
bNeedsUpdate = true;
}
}
if ( bNeedsUpdate )
{
m_edgeHash.Remove( edgeHashIndex );
if ( pEdge->m_nVert[0] == pEdge->m_nVert[1] )
{
pEdge->MarkCollapsed();
}
else
{
UpdateEdgeError(pEdge);
tmp.m_nV0 = pEdge->m_nVert[0];
tmp.m_nV1 = pEdge->m_nVert[1];
if ( tmp.m_nV0 > tmp.m_nV1 ) // swap so min is in m_nV0
{
tmp.m_nV0 = tmp.m_nV1;
tmp.m_nV1 = pEdge->m_nVert[0];
}
uint nHashKey = VertHashKey(tmp.m_nV0, tmp.m_nV1);
if ( m_edgeHash.Find( nHashKey, tmp ) != m_edgeHash.InvalidHandle() )
{
// another edge with these indices exists in the table, mark as collapsed
pEdge->MarkCollapsed();
}
else
{
m_edgeHash.Insert( nHashKey, tmp );
}
}
}
}
void CMeshVisit::Get1Ring( CUniqueVertexList &list, uint32 nVertex )
{
int nTriCount = m_vertList[nVertex].m_triangles.Count();
for ( int i = 0; i < nTriCount; i++ )
{
const vertex_triangle_t &tri = m_vertList[nVertex].m_triangles[i];
list.AddIfUnique( tri.nV1 );
list.AddIfUnique( tri.nV2 );
}
}
// Counts the number of adjacent verts shared by a pair of verts
// This is used to prevent creating shark fin topologies
int CMeshVisit::CountSharedVerts( int nVert0, int nVert1 )
{
CUniqueVertexList vertIndex0;
CUniqueVertexList vertIndex1;
Get1Ring( vertIndex0, nVert0 );
Get1Ring( vertIndex1, nVert1 );
int nSharedCount = 0;
for ( int i = 0; i < vertIndex1.Count(); i++ )
{
if ( vertIndex0.Find(vertIndex1[i]) != -1 )
{
nSharedCount++;
}
}
return nSharedCount;
}
// Heuristics for avoiding collapsing edges that will generate bad topology
bool CMeshVisit::IsValidCollapseVertex( int nVertCheck, int nVertOpposite, const Vector &vReplacePos )
{
Vector vOld = GetVertexPosition(nVertCheck);
int nTriCount = m_vertList[nVertCheck].m_triangles.Count();
for ( int i = 0; i < nTriCount; i++ )
{
const vertex_triangle_t &tri = m_vertList[nVertCheck].m_triangles[i];
int nV1 = tri.nV1;
int nV2 = tri.nV2;
// this triangle has the collapsing edge, skip it
if ( nV1 == nVertOpposite || nV2 == nVertOpposite )
continue;
Vector v1 = GetVertexPosition(nV1);
Vector v2 = GetVertexPosition(nV2);
Vector vEdge0 = v1 - vOld;
Vector vEdge1 = v2 - vOld;
Vector vNormal = CrossProduct( vEdge1, vEdge0 );
Vector vNewEdge0 = v1 - vReplacePos;
Vector vNewEdge1 = v2 - vReplacePos;
Vector vNewNormal = CrossProduct( vNewEdge1, vNewEdge0 );
float flDot = DotProduct( vNewNormal, vNormal );
// If the collapse will flip the face, avoid it
if ( flDot < 0.0f )
return false;
}
return true;
}
bool CMeshVisit::IsValidCollapse( int nMinEdge )
{
VPROF("IsValidcollapse");
if ( nMinEdge < 0 )
return true;
if ( m_edgeList[nMinEdge].m_bNonManifold )
return false;
int nVert0 = m_edgeList[nMinEdge].m_nVert[0];
int nVert1 = m_edgeList[nMinEdge].m_nVert[1];
// This constraint should keep shark-fin like geometries from forming
if ( CountSharedVerts( nVert0, nVert1 ) > 2 )
return false;
Vector vOptimal = m_edgeList[nMinEdge].m_vOptimal;
return IsValidCollapseVertex( nVert0, nVert1, vOptimal ) && IsValidCollapseVertex( nVert1, nVert0, vOptimal );
}
void CMeshVisit::CollapseEdge( int nCollapse )
{
VPROF("CollapseEdge");
m_edgeList[nCollapse].MarkCollapsed();
Vector vOptimal = m_edgeList[nCollapse].m_vOptimal;
// get the vert being removed
Assert(nCollapse < m_edgeList.Count() );
int nV0 = m_edgeList[nCollapse].m_nVert[0];
int nV1 = m_edgeList[nCollapse].m_nVert[1];
Assert( nV0 < m_vertList.Count() );
Assert( nV1 < m_vertList.Count() );
float flInterp = m_edgeList[nCollapse].m_flInterp;
uint32 nVertexKeep = nV0;
uint32 nVertexRemove = nV1;
if ( m_edgeList[nCollapse].m_nBestIndex == 1 )
{
nVertexKeep = m_edgeList[nCollapse].m_nVert[1];
nVertexRemove = m_edgeList[nCollapse].m_nVert[0];
}
// propagate the error
m_errorVert[nVertexKeep] += m_errorVert[nVertexRemove];
m_errorVert[nVertexKeep] *= m_flIntegrationPenalty;
CUniqueVertexList vertIndex;
Get1Ring( vertIndex, nVertexRemove );
// @TODO: Need to copy triangles over if we allow merging unconnected pairs
// Assert that this pair is connected
// Assert( vertIndex.Find(nVertexKeep) != -1 );
int nTrianglesRemoved = 0;
CUtlVectorFixedGrowable<uint32, 32> removeList;
for ( int i = 0; i < vertIndex.Count(); i++ )
{
uint32 nVertex = vertIndex[i];
for ( int j = 0; j < m_vertList[nVertex].m_triangles.Count(); j++ )
{
vertex_triangle_t &tri = m_vertList[nVertex].m_triangles[j];
if ( tri.nV1 != nVertexRemove && tri.nV2 != nVertexRemove )
continue;
if ( tri.nV1 == nVertexRemove )
{
tri.nV1 = nVertexKeep;
}
if ( tri.nV2 == nVertexRemove )
{
tri.nV2 = nVertexKeep;
}
if ( tri.nV1 == tri.nV2 || tri.nV1 == nVertex || tri.nV2 == nVertex )
{
removeList.AddToTail(j);
}
}
nTrianglesRemoved += removeList.Count();
for ( int j = removeList.Count(); --j >= 0; )
{
m_vertList[nVertex].m_triangles.FastRemove(removeList[j]);
}
removeList.RemoveAll();
}
for ( int i = 0; i < vertIndex.Count(); i++ )
{
RemapEdge( nVertexRemove, vertIndex[i], nVertexKeep );
}
int nRemoveCount = 0;
for ( int j = 0; j < m_vertList[nVertexRemove].m_triangles.Count(); j++ )
{
vertex_triangle_t &tri = m_vertList[nVertexRemove].m_triangles[j];
if ( tri.nV1 == nVertexKeep || tri.nV2 == nVertexKeep )
{
nRemoveCount++;
continue;
}
m_vertList[nVertexKeep].m_triangles.AddToTail( tri );
}
nTrianglesRemoved += nRemoveCount;
// These triangles are all invalid now
m_vertList[nVertexRemove].m_triangles.RemoveAll();
Assert( (nTrianglesRemoved % 3) == 0 );
// each triangle has 3 copies in the list (one at each vert) so the number of real triangles removed
// is 1/3rd of the number removed from the vertex lists
m_nTriangleCount -= (nTrianglesRemoved / 3);
LerpVertex( GetVertex(nVertexKeep), GetVertex(nV0), GetVertex(nV1), flInterp, m_nVertexStrideFloats );
m_nCollapseIndex++;
}
int CMeshVisit::CountTotalUsedVerts()
{
int nVertCount = m_vertList.Count();
int nMaxVertexIndex = m_vertList.Count();
CUtlVector<int> used;
used.SetCount( nMaxVertexIndex + 1 );
used.FillWithValue( 0 );
int nUsedCount = 0;
for ( int i = 0; i < nVertCount; i++ )
{
int nTriCount = m_vertList[i].m_triangles.Count();
if ( nTriCount )
{
if ( !used[i] )
{
used[i] = 1;
nUsedCount++;
}
}
for ( int j = 0; j < nTriCount; j++ )
{
if ( !used[m_vertList[i].m_triangles[j].nV1] )
{
used[m_vertList[i].m_triangles[j].nV1] = 1;
nUsedCount++;
}
if ( !used[m_vertList[i].m_triangles[j].nV2] )
{
used[m_vertList[i].m_triangles[j].nV2] = 1;
nUsedCount++;
}
}
}
return nUsedCount;
}
int CountUsedVerts( const uint32 *pIndexList, int nIndexCount, int nVertexCount )
{
CUtlVector<uint8> used;
used.SetCount(nVertexCount);
for ( int i = 0; i < nVertexCount; i++ )
{
used[i] = 0;
}
int nUsedCount = 0;
for ( int i = 0; i < nIndexCount; i++ )
{
int nIndex = pIndexList[i];
if ( !used[nIndex] )
{
used[nIndex] = 1;
nUsedCount++;
}
}
return nUsedCount;
}
void CMeshVisit::BuildFromMesh( const CMesh &input )
{
VPROF("InitFromSimpleMesh");
// NOTE: This assumes that position is the FIRST float3 in the buffer
int nPosOffset = input.FindFirstAttributeOffset( VERTEX_ELEMENT_POSITION );
if ( nPosOffset != 0 )
{
return;
}
int nInputVertCount = input.m_nVertexCount;
int nInputIndexCount = input.m_nIndexCount;
int nInputTriangleCount = nInputIndexCount / 3;
m_nTriangleCount = nInputTriangleCount;
m_edgeHash.Init( nInputIndexCount * 2 );
// reserve space for vertex tables
m_vertList.SetCount( nInputVertCount );
for ( int i = 0; i < nInputVertCount; i++ )
{
m_vertList[i].m_triangles.EnsureCapacity( 8 );
}
m_edgeList.EnsureCapacity( nInputTriangleCount * 3 * 2 );
// check for degenerate triangles in debug builds
#if _DEBUG
for ( int i = 0; i < nInputIndexCount; i += 3 )
{
if ( input.m_pIndices[i+0] == input.m_pIndices[i+1] || input.m_pIndices[i+0] == input.m_pIndices[i+2] || input.m_pIndices[i+1] == input.m_pIndices[i+2] )
{
// Found a degenerate triangle
// use CleanMesh() to remove degenerates if you aren't sure if the mesh contains degenerates
// The simplification code does not tolerate degenerate triangles
Assert(0);
}
}
#endif
for ( int i = 0; i < nInputIndexCount; i += 3 )
{
for ( int j = 0; j < 3; j++ )
{
int nV0 = input.m_pIndices[i+j];
int nNext = (j+1)%3;
int nV1 = input.m_pIndices[i+nNext];
edge_hash_t tmp;
tmp.m_nV0 = MIN(nV0, nV1);
tmp.m_nV1 = MAX(nV0, nV1);
uint nHashKey = VertHashKey(MIN(nV0, nV1), MAX(nV0, nV1));
tmp.m_pSharedEdge = NULL;
UtlHashFastHandle_t edgeHashIndex = m_edgeHash.Find( nHashKey, tmp );
// new edge, initialize it
if ( m_edgeHash.InvalidHandle() == edgeHashIndex )
{
int nEdgeIndex = m_edgeList.AddToTail();
tmp.m_pSharedEdge = &m_edgeList[nEdgeIndex];
tmp.m_pSharedEdge->InitVerts( tmp.m_nV0, tmp.m_nV1 );
edgeHashIndex = m_edgeHash.Insert( nHashKey, tmp );
}
edge_hash_t &edgeHash = m_edgeHash.Element( edgeHashIndex );
edgeHash.m_pSharedEdge->m_nReferences++;
vertex_triangle_t tri;
tri.nV1 = nV1;
tri.nV2 = input.m_pIndices[ i + ((j+2)%3) ];
m_vertList[nV0].m_triangles.AddToTail( tri );
}
}
m_nInputVertCount = input.m_nVertexCount;
m_vertData.SetCount( input.m_nVertexCount * input.m_nVertexStrideFloats );
m_pVertexBase = m_vertData.Base();
m_nVertexStrideFloats = input.m_nVertexStrideFloats;
V_memcpy( m_pVertexBase, input.GetVertex(0), input.GetTotalVertexSizeInBytes() );
m_nCollapseIndex = 0;
}
// Removes elements from the queue until a valid one is found, returns that index or -1 indicating there are no valid edges
int CMeshVisit::FindMinErrorEdge()
{
VPROF("FindMinErrorEdge");
int nBest = -1;
while ( true )
{
if ( !m_edgeQueue.Count() )
return -1;
edge_queue_entry_t entry = m_edgeQueue.ElementAtHead();
m_edgeQueue.RemoveAtHead();
CQEMEdge &edge = m_edgeList[entry.m_nEdgeIndex];
if ( edge.m_flCurrentError == entry.m_flError && !edge.IsCollapsed() )
{
if ( IsValidCollapse( entry.m_nEdgeIndex ) )
{
nBest = entry.m_nEdgeIndex;
break;
}
}
}
return nBest;
}
void CMeshVisit::ComputeVertListError( float flOpenEdgePenalty, float flMinArea, float flMaxArea, const mesh_simplifyweights_t *pWeights )
{
m_errorVert.SetCount( m_nInputVertCount );
if ( pWeights )
{
// can we use all of the weights? If not, don't use any of them
if ( pWeights->m_nVertexCount != m_nInputVertCount )
{
pWeights = NULL;
}
}
for ( int i = 0; i < m_nInputVertCount; i++ )
{
m_errorVert[i].SetToZero();
for ( int j = 0; j < m_vertList[i].m_triangles.Count(); j++ )
{
int nV0 = i;
int nV1 = m_vertList[i].m_triangles[j].nV1;
int nV2 = m_vertList[i].m_triangles[j].nV2;
if ( IsOpenEdge( nV0, nV1 ) )
{
Vector v0 = GetVertexPosition(nV0);
Vector v1 = GetVertexPosition(nV1);
Vector v2 = GetVertexPosition(nV2);
Vector vNormal = CrossProduct( v2 - v0, v1 - v0 );
Vector vPlaneNormal = CrossProduct( v1 - v0, vNormal );
float flArea = 0.5f * vPlaneNormal.NormalizeInPlace() * flOpenEdgePenalty;
CQuadricError errorThisEdge;
errorThisEdge.InitFromPlane( vPlaneNormal, -DotProduct(vPlaneNormal, v0), flArea );
m_errorVert[i] += errorThisEdge;
continue;
}
CQuadricError errorThisTri;
errorThisTri.InitFromTriangle( GetVertexPosition(nV0), GetVertexPosition(nV1), GetVertexPosition(nV2), flMinArea );
m_errorVert[i] += errorThisTri;
}
if ( pWeights && pWeights->m_pVertexWeights )
{
m_errorVert[i] *= pWeights->m_pVertexWeights[i];
}
}
int nInputEdgeCount = m_edgeList.Count();
for ( int i = 0; i < nInputEdgeCount; i++ )
{
UpdateEdgeError( &m_edgeList[i] );
}
}
void GetOpenEdges( CUtlVector<Vector> &list, const CMesh &input )
{
CMeshVisit visit;
visit.BuildFromMesh( input );
for ( int i = 0; i < visit.m_edgeList.Count(); i++ )
{
if ( visit.m_edgeList[i].m_nReferences < 2 )
{
list.AddToTail( visit.GetVertexPosition( visit.m_edgeList[i].m_nVert[0] ) );
list.AddToTail( visit.GetVertexPosition( visit.m_edgeList[i].m_nVert[1] ) );
}
}
}
void SimplifyMeshQEM2( CMesh &meshOut, const CMesh &input, const mesh_simplifyparams_t &params, const mesh_simplifyweights_t *pWeights )
{
VPROF("Simplify");
CMeshVisit visit;
visit.BuildFromMesh( input );
CUtlVector<CQuadricError> errorEdge;
int nInputVertCount = input.m_nVertexCount;
int nInputEdgeCount = visit.m_edgeList.Count();
errorEdge.SetCount( nInputEdgeCount );
visit.m_flIntegrationPenalty = params.m_flIntegrationPenalty;
visit.ComputeVertListError( params.m_flOpenEdgePenalty, 0.0f, 1.0f, pWeights );
int nMinEdge = visit.FindMinErrorEdge();
int nVertexCurrent = CountUsedVerts( input.m_pIndices, input.m_nIndexCount, input.m_nVertexCount );
if ( nMinEdge >= 0 )
{
float flMinError = visit.m_edgeList[nMinEdge].m_flCurrentError;
while ( flMinError < params.m_flMaxError || nVertexCurrent > params.m_nMaxVertexCount || visit.m_nTriangleCount > params.m_nMaxTriangleCount )
{
visit.CollapseEdge( nMinEdge );
nVertexCurrent--;
// don't collapse to anything two dimensional
if ( nVertexCurrent < 5 )
return;
nMinEdge = visit.FindMinErrorEdge();
if ( nMinEdge < 0 )
break;
flMinError = visit.m_edgeList[nMinEdge].m_flCurrentError;
}
}
Vprof_MarkFrame_IfEnabled();
CUtlVector<uint32> indexOut;
visit.WriteMeshIndexList( indexOut );
int nOutputIndexCount = indexOut.Count();
const uint32 nInvalidIndex = uint32(-1);
CUtlVector<uint32> nIndexMap;
nIndexMap.SetCount( nInputVertCount );
nIndexMap.FillWithValue( nInvalidIndex );
int nOutputVertexCount = 0;
for ( int i = 0; i < nOutputIndexCount; i++ )
{
int nIndex = indexOut[i];
if ( nIndexMap[nIndex] == nInvalidIndex )
{
nIndexMap[nIndex] = nOutputVertexCount;
nOutputVertexCount++;
}
indexOut[i] = nIndexMap[nIndex];
}
meshOut.AllocateMesh( nOutputVertexCount, nOutputIndexCount, input.m_nVertexStrideFloats, input.m_pAttributes, input.m_nAttributeCount );
for ( int i = 0; i < nOutputIndexCount; i++ )
{
meshOut.m_pIndices[i] = indexOut[i];
}
for ( int i = 0; i < nInputVertCount; i++ )
{
if ( nIndexMap[i] != nInvalidIndex )
{
V_memcpy( meshOut.GetVertex(nIndexMap[i]), visit.GetVertex(i), meshOut.m_nVertexStrideFloats * sizeof(float) );
}
}
#if _DEBUG
int nDeltaIndex = input.m_nIndexCount - indexOut.Count();
Msg("Simplified. Removed %d triangles (now %d was %d) (now %d verts, was %d)\n", nDeltaIndex / 3, indexOut.Count() / 3, input.m_nIndexCount / 3, nOutputVertexCount, nInputVertCount );
#endif
}
void SimplifyMesh( CMesh &meshOut, const CMesh &input, const mesh_simplifyparams_t &params, const mesh_simplifyweights_t *pWeights )
{
Vprof_Start_IfEnabled();
SimplifyMeshQEM2( meshOut, input, params, pWeights);
Vprof_Report_IfEnabled();
}