This commit is contained in:
nephacks
2025-06-04 03:22:50 +02:00
parent f234f23848
commit f12416cffd
14243 changed files with 6446499 additions and 26 deletions

View File

@@ -0,0 +1,58 @@
// ----------------------------------------- //
// File generated by VPC //
// ----------------------------------------- //
Source file: F:\csgo_64\cstrike15_src\public\interpolatortypes.cpp
Debug output file: F:\csgo_64\cstrike15_src\public\interpolatortypes.cpp
Release output file: F:\csgo_64\cstrike15_src\public\interpolatortypes.cpp
Containing unity file:
PCH file:
Source file: F:\csgo_64\cstrike15_src\meshutils\atlas.cpp
Debug output file: F:\csgo_64\cstrike15_src\meshutils\atlas.cpp
Release output file: F:\csgo_64\cstrike15_src\meshutils\atlas.cpp
Containing unity file:
PCH file:
Source file: F:\csgo_64\cstrike15_src\meshutils\clipmesh.cpp
Debug output file: F:\csgo_64\cstrike15_src\meshutils\clipmesh.cpp
Release output file: F:\csgo_64\cstrike15_src\meshutils\clipmesh.cpp
Containing unity file:
PCH file:
Source file: F:\csgo_64\cstrike15_src\meshutils\convexhull.cpp
Debug output file: F:\csgo_64\cstrike15_src\meshutils\convexhull.cpp
Release output file: F:\csgo_64\cstrike15_src\meshutils\convexhull.cpp
Containing unity file:
PCH file:
Source file: F:\csgo_64\cstrike15_src\common\debug_lib_check.cpp
Debug output file: F:\csgo_64\cstrike15_src\common\debug_lib_check.cpp
Release output file: F:\csgo_64\cstrike15_src\common\debug_lib_check.cpp
Containing unity file:
PCH file:
Source file: F:\csgo_64\cstrike15_src\meshutils\mesh.cpp
Debug output file: F:\csgo_64\cstrike15_src\meshutils\mesh.cpp
Release output file: F:\csgo_64\cstrike15_src\meshutils\mesh.cpp
Containing unity file:
PCH file:
Source file: F:\csgo_64\cstrike15_src\meshutils\simplify.cpp
Debug output file: F:\csgo_64\cstrike15_src\meshutils\simplify.cpp
Release output file: F:\csgo_64\cstrike15_src\meshutils\simplify.cpp
Containing unity file:
PCH file:
Source file: F:\csgo_64\cstrike15_src\meshutils\uvparam.cpp
Debug output file: F:\csgo_64\cstrike15_src\meshutils\uvparam.cpp
Release output file: F:\csgo_64\cstrike15_src\meshutils\uvparam.cpp
Containing unity file:
PCH file:
Source file: F:\csgo_64\cstrike15_src\meshutils\vertexops.cpp
Debug output file: F:\csgo_64\cstrike15_src\meshutils\vertexops.cpp
Release output file: F:\csgo_64\cstrike15_src\meshutils\vertexops.cpp
Containing unity file:
PCH file:

274
meshutils/atlas.cpp Normal file
View File

@@ -0,0 +1,274 @@
//=========== Copyright <20> Valve Corporation, All rights reserved. ============//
//
// Purpose: Mesh class UV parameterization operations.
//
//===========================================================================//
#include "mesh.h"
class CPackNode
{
public:
CPackNode* m_child[2];
Rect_t m_rect;
AtlasChart_t* m_pChart;
float m_flTotalW;
float m_flTotalH;
public:
CPackNode( Rect_t rect, float flTotalW, float flTotalH );
~CPackNode();
CPackNode *InsertChart( AtlasChart_t* pTexture );
};
class CAtlasPacker
{
private:
int m_nWidth;
int m_nHeight;
CPackNode *m_pRootNode;
public:
CAtlasPacker();
~CAtlasPacker();
void Init( int nWidth, int nHeight );
bool InsertChart( AtlasChart_t *pTexture );
};
int SortAtlasCharts( AtlasChart_t* const *pOne, AtlasChart_t* const *pTwo )
{
int nSizeOne = MAX( (*pOne)->m_vMaxTextureSize.x, (*pOne)->m_vMaxTextureSize.y );
int nSizeTwo = MAX( (*pTwo)->m_vMaxTextureSize.x, (*pTwo)->m_vMaxTextureSize.y );
if ( nSizeOne < nSizeTwo )
return -1;
else if ( nSizeOne > nSizeTwo )
return 1;
return 0;
}
//--------------------------------------------------------------------------------------
// Pack charts into an atlas. If nAtlasGrow is non-zero, we will attempt to create an atlas
// starting at nAtlasTextureSizeX and growing by nAtlasGrow every time until we eventually
// get an atlas. If nAtlasGrow is 0, then we return the number of charts that didn't
// get atlased.
//--------------------------------------------------------------------------------------
int PackChartsIntoAtlas( AtlasChart_t *pCharts, int nCharts, int nAtlasTextureSizeX, int nAtlasTextureSizeY, int nAtlasGrow )
{
// Create a duplicate vector to sort so that the input remains in the same order
CUtlVector<AtlasChart_t*> chartVector;
CUtlVector<bool> chartUsed;
chartVector.EnsureCount( nCharts );
chartUsed.EnsureCount( nCharts );
for ( int c=0; c<nCharts; ++c )
{
chartVector[ c ] = &pCharts[ c ];
chartUsed[ c ] = false;
}
chartVector.Sort( SortAtlasCharts );
// Try to get the most out of our texture space
bool bTryGrow = ( nAtlasGrow > 0 );
bool bHaveAtlas = false;
int nAtlasSizeX = nAtlasTextureSizeX;
int nAtlasSizeY = nAtlasTextureSizeY;
int nAttempt = 0;
int nUnatlased = 0;
while ( !bHaveAtlas )
{
Msg( "Atlas Attempt: %d\n", nAttempt );
// assume we have an atlas
bHaveAtlas = true;
// increment and try again
nAtlasSizeX += nAtlasGrow;
nAtlasSizeY += nAtlasGrow;
CAtlasPacker m_packer;
m_packer.Init( nAtlasSizeX, nAtlasSizeY );
// insert largest first
for ( int t=nCharts-1; t>=0; --t )
{
AtlasChart_t *pChart = chartVector[ t ];
if ( !pChart->m_bAtlased )
{
if ( m_packer.InsertChart( pChart ) )
{
pChart->m_bAtlased = true;
}
else
{
if ( bTryGrow )
{
bHaveAtlas = false;
nAttempt++;
break;
}
else
{
nUnatlased++;
}
}
}
}
}
return nUnatlased;
}
//CPackNode
CPackNode::CPackNode( Rect_t rect, float flTotalW, float flTotalH ) :
m_pChart( NULL ),
m_flTotalW( flTotalW ),
m_flTotalH( flTotalH )
{
m_child[0] = NULL;
m_child[1] = NULL;
m_rect = rect;
}
CPackNode::~CPackNode()
{
if ( m_child[ 0 ] ) { delete m_child[ 0 ]; }
if ( m_child[ 1 ] ) { delete m_child[ 1 ]; }
}
CPackNode* CPackNode::InsertChart( AtlasChart_t *pChart )
{
int texWidth = (int)ceil( pChart->m_vMaxTextureSize.x );
int texHeight = (int)ceil( pChart->m_vMaxTextureSize.y );
//if we have children, that means we can't insert into this node, try the kids
if ( NULL != m_child[ 0 ] && NULL != m_child[ 1 ] )
{
//try the first child
CPackNode* pNewNode = m_child[ 0 ]->InsertChart( pChart );
if(pNewNode)
return pNewNode;
//if that didn't work, try the second
pNewNode = m_child[ 1 ]->InsertChart( pChart );
return pNewNode; //if this didn't work it will return NULL
}
else //else, see if we can fit it in
{
//if we are a leaf of the tree (m_child[0] and m_child[1] have textures in them,
//then make sure we don't have texture already in here
if ( m_pChart )
return NULL; //if we already have a texture, return NULL
//else, see if we can even fit the lightmap
int width = m_rect.width;// + 1;
int height = m_rect.height;// + 1;
if ( width < texWidth || height < texHeight )
return NULL; //we don't fit!!!
//if we're just the right size, then add the lightmap and we're done
if ( width == texWidth && height == texHeight )
{
m_pChart = pChart; //mark this as the texture for the current node
//get the new texture coordinates and put them in the texture
{
m_pChart->m_vAtlasMin.x = m_rect.x / m_flTotalW;
m_pChart->m_vAtlasMin.y = m_rect.y / m_flTotalH;
m_pChart->m_vAtlasMax.x = ( m_rect.x + m_rect.width ) / m_flTotalW;
m_pChart->m_vAtlasMax.y = ( m_rect.y + m_rect.height ) / m_flTotalH;
}
return this; //return us, since we're the right size
}
//if we're not the right size, but we're big enough to hold the lightmap,
//split us up into two nodes
Rect_t rect0,rect1;
int dw = width - texWidth;
int dh = height - texHeight;
if( dw > dh ) //split left, right
{
//left rect
rect0.x = m_rect.x;
rect0.width = texWidth;// - 1;
rect0.y = m_rect.y;
rect0.height = m_rect.height;
//right rect
rect1.x = m_rect.x + rect0.width;
rect1.width = m_rect.width - rect0.width;
rect1.y = m_rect.y;
rect1.height = m_rect.height;
}
else //split up, down
{
//top rect
rect0.x = m_rect.x;
rect0.width = m_rect.width;
rect0.y = m_rect.y;
rect0.height = texHeight;// - 1;
//bottom rect
rect1.x = m_rect.x;
rect1.width = m_rect.width;
rect1.y = m_rect.y + rect0.height;
rect1.height = m_rect.height - rect0.height;
}
m_child[ 0 ] = new CPackNode( rect0, m_flTotalW, m_flTotalH );
m_child[ 1 ] = new CPackNode( rect1, m_flTotalW, m_flTotalH );
//since we made the first child the size we needed, insert into him.
//this should never fail
return m_child[ 0 ]->InsertChart( pChart );
}
}
//clightmappacker class
CAtlasPacker::CAtlasPacker()
{
m_nWidth = 0;
m_nHeight = 0;
m_pRootNode = NULL;
}
CAtlasPacker::~CAtlasPacker()
{
if ( m_pRootNode ) delete m_pRootNode;
}
void CAtlasPacker::Init( int nWidth, int nHeight )
{
m_nWidth = nWidth;
m_nHeight = nHeight;
Rect_t rect;
rect.x = 0; rect.width = nWidth;
rect.y = 0; rect.height = nHeight;
m_pRootNode = new CPackNode( rect, (float)nWidth, (float)nHeight );
}
bool CAtlasPacker::InsertChart( AtlasChart_t *pChart )
{
if( !m_pRootNode )
return false;
CPackNode* pNode = m_pRootNode->InsertChart( pChart );
if(pNode)
{
return true;
}
return false;
}

274
meshutils/clipmesh.cpp Normal file
View File

@@ -0,0 +1,274 @@
//=========== Copyright <20> Valve Corporation, All rights reserved. ============//
//
// Purpose: Mesh clipping operations.
//
//===========================================================================//
#include "mesh.h"
#include "tier1/utlbuffer.h"
void ClipTriangle( float *pBackOut, float *pFrontOut, int *pNumBackOut, int *pNumFrontOut, int nStrideFloats,
float **ppVertsIn, Vector4D &vClipPlane )
{
int nBack = 0;
int nFront = 0;
Vector vPlaneNormal = vClipPlane.AsVector3D();
const int nVerts = 3;
for( int v=0; v<nVerts; ++v )
{
int nxtVert = ( v + 1 ) % nVerts;
Vector vPos1 = *(Vector*)ppVertsIn[ v ];
Vector vPos2 = *(Vector*)ppVertsIn[ nxtVert ];
float flDot1 = DotProduct( vPos1, vPlaneNormal ) + vClipPlane.w;
float flDot2 = DotProduct( vPos2, vPlaneNormal ) + vClipPlane.w;
// Enforce that points that lie perfectly on the plane always go to the front
if ( flDot1 == 0.0f )
{
flDot1 = 0.01f;
}
if ( flDot2 == 0.0f )
{
flDot2 = 0.01f;
}
if ( flDot1 < 0 )
{
CopyVertex( pBackOut + nBack * nStrideFloats, ppVertsIn[ v ], nStrideFloats );
nBack ++;
}
else
{
CopyVertex( pFrontOut + nFront * nStrideFloats, ppVertsIn[ v ], nStrideFloats );
nFront ++;
}
if ( flDot1 * flDot2 < 0 )
{
// Lerp verts
float flLerp = -flDot1 / ( flDot2 - flDot1 );
LerpVertex( pBackOut + nBack * nStrideFloats, ppVertsIn[ v ], ppVertsIn[ nxtVert ], flLerp, nStrideFloats );
CopyVertex( pFrontOut + nFront * nStrideFloats, pBackOut + nBack * nStrideFloats, nStrideFloats );
nBack ++;
nFront++;
}
}
*pNumBackOut = nBack;
*pNumFrontOut = nFront;
}
// Clips a mesh against a plane and returns 2 meshes, one on each side of the plane. The caller must
// check pMeshBack and pMeshFront for empty vertex or index sets implying that the mesh was entirely
// on one side of the plane or the other.
bool ClipMeshToHalfSpace( CMesh *pMeshBack, CMesh *pMeshFront, const CMesh &inputMesh, Vector4D &vClipPlane )
{
Assert( pMeshBack || pMeshFront );
// NOTE: This assumes that position is the FIRST float3 in the buffer
int nPosOffset = inputMesh.FindFirstAttributeOffset( VERTEX_ELEMENT_POSITION );
if ( nPosOffset != 0 )
return false;
int nVertexStrideFloats = inputMesh.m_nVertexStrideFloats;
int nIndexCount = inputMesh.m_nIndexCount;
uint32 *pIndices = inputMesh.m_pIndices;
float *pVertices = inputMesh.m_pVerts;
// Allocate working space for the number of vertices out on each side of the plane.
// This is a maximum of 4 vertices out when clipping a triangle to a plane.
float *pBackOut = new float[ nVertexStrideFloats * 4 ];
float *pFrontOut = new float[ nVertexStrideFloats * 4 ];
CUtlBuffer backMeshVerts;
CUtlBuffer frontMeshVerts;
for ( int i=0; i<nIndexCount; i += 3 )
{
float *ppVerts[3];
int nIndex0 = pIndices[ i ];
int nIndex1 = pIndices[ i + 1 ];
int nIndex2 = pIndices[ i + 2 ];
ppVerts[0] = pVertices + nIndex0 * nVertexStrideFloats;
ppVerts[1] = pVertices + nIndex1 * nVertexStrideFloats;
ppVerts[2] = pVertices + nIndex2 * nVertexStrideFloats;
int nBackOut = 0;
int nFrontOut = 0;
ClipTriangle( pBackOut, pFrontOut, &nBackOut, &nFrontOut, nVertexStrideFloats, ppVerts, vClipPlane );
// reconstruct triangles out of the polygon
int numBackTris = nBackOut - 2;
for ( int t=0; t<numBackTris; ++t )
{
backMeshVerts.Put( pBackOut, nVertexStrideFloats * sizeof( float ) );
backMeshVerts.Put( pBackOut + nVertexStrideFloats * ( t + 1 ), nVertexStrideFloats * sizeof( float ) );
backMeshVerts.Put( pBackOut + nVertexStrideFloats * ( t + 2 ), nVertexStrideFloats * sizeof( float ) );
}
int numFrontTris = nFrontOut - 2;
for ( int t=0; t<numFrontTris; ++t )
{
frontMeshVerts.Put( pFrontOut, nVertexStrideFloats * sizeof( float ) );
frontMeshVerts.Put( pFrontOut + nVertexStrideFloats * ( t + 1 ), nVertexStrideFloats * sizeof( float ) );
frontMeshVerts.Put( pFrontOut + nVertexStrideFloats * ( t + 2 ), nVertexStrideFloats * sizeof( float ) );
}
}
delete []pBackOut;
delete []pFrontOut;
// Turn the utlbuffers into actual meshes
if ( pMeshBack )
{
pMeshBack->m_nVertexCount = backMeshVerts.TellPut() / ( nVertexStrideFloats * sizeof( float ) );
if ( pMeshBack->m_nVertexCount )
{
pMeshBack->AllocateMesh( pMeshBack->m_nVertexCount, pMeshBack->m_nVertexCount, nVertexStrideFloats, inputMesh.m_pAttributes, inputMesh.m_nAttributeCount );
Q_memcpy( pMeshBack->m_pVerts, backMeshVerts.Base(), backMeshVerts.TellPut() );
for ( int i=0; i<pMeshBack->m_nIndexCount; ++i )
{
pMeshBack->m_pIndices[i] = i;
}
}
}
if ( pMeshFront )
{
pMeshFront->m_nVertexCount = frontMeshVerts.TellPut() / ( nVertexStrideFloats * sizeof( float ) );
if ( pMeshFront->m_nVertexCount )
{
pMeshFront->AllocateMesh( pMeshFront->m_nVertexCount, pMeshFront->m_nVertexCount, nVertexStrideFloats, inputMesh.m_pAttributes, inputMesh.m_nAttributeCount );
Q_memcpy( pMeshFront->m_pVerts, frontMeshVerts.Base(), frontMeshVerts.TellPut() );
for ( int i=0; i<pMeshFront->m_nIndexCount; ++i )
{
pMeshFront->m_pIndices[i] = i;
}
}
}
return true;
}
//--------------------------------------------------------------------------------------
void CreateGridCellsForVolume( CUtlVector< GridVolume_t > &outputVolumes, const Vector &vTotalMinBounds, const Vector &vTotalMaxBounds, const Vector &vGridSize )
{
// First, determine if we need to be split
Vector vDelta = vTotalMaxBounds - vTotalMinBounds;
int nX = vDelta.x / vGridSize.x;
int nY = vDelta.y / vGridSize.y;
int nZ = vDelta.z / vGridSize.z;
Vector vEpsilon( 0.1f, 0.1f, 0.1f );
Vector vMinBounds = vTotalMinBounds - vEpsilon;
Vector vMaxBounds = vTotalMaxBounds + vEpsilon;
if ( nX * nY * nZ < 2 )
{
GridVolume_t newbounds;
newbounds.m_vMinBounds = vMinBounds;
newbounds.m_vMaxBounds = vMaxBounds;
outputVolumes.AddToTail( newbounds );
}
else
{
Vector vStep;
vStep.z = vDelta.z / nZ;
vStep.y = vDelta.y / nY;
vStep.x = vDelta.x / nX;
// Create the split volumes
outputVolumes.EnsureCount( nX * nY * nZ );
int nVolumes = 0;
Vector vStart = vMinBounds;
for ( int z=0; z<nZ; ++z )
{
vStart.y = vMinBounds.y;
for ( int y=0; y<nY; ++y )
{
vStart.x = vMinBounds.x;
for ( int x=0; x<nX; ++x )
{
GridVolume_t newbounds;
newbounds.m_vMinBounds = vStart;
newbounds.m_vMaxBounds = vStart + vStep;
if ( x == nX - 1 )
newbounds.m_vMaxBounds.x = vMaxBounds.x;
if ( y == nY - 1 )
newbounds.m_vMaxBounds.y = vMaxBounds.y;
if ( z == nZ - 1 )
newbounds.m_vMaxBounds.z = vMaxBounds.z;
outputVolumes[ nVolumes ] = newbounds;
nVolumes ++;
vStart.x += vStep.x;
}
vStart.y += vStep.y;
}
vStart.z += vStep.z;
}
}
}
//--------------------------------------------------------------------------------------
// For a list of AABBs, find all indices in the mesh that belong to each AABB. Then
// coalesce those indices into a single buffer in order of the input AABBs and return a
// list of ranges of the indices in the output mesh that are needed in each input volume
//--------------------------------------------------------------------------------------
void CreatedGriddedIndexRangesFromMesh( CMesh *pOutputMesh, CUtlVector< IndexRange_t > &outputRanges, const CMesh &inputMesh, CUtlVector< GridVolume_t > &inputVolumes )
{
DuplicateMesh( pOutputMesh, inputMesh );
int nVolumes = inputVolumes.Count();
outputRanges.EnsureCount( nVolumes );
int nFaces = inputMesh.m_nIndexCount / 3;
uint32 *pInputIndices = inputMesh.m_pIndices;
uint32 *pOutputIndices = pOutputMesh->m_pIndices;
int nMeshIndices = 0;
// Go though each volume and assign indices to its respective range
for ( int v=0; v<nVolumes; ++v )
{
GridVolume_t &volume = inputVolumes[ v ];
IndexRange_t &range = outputRanges[ v ];
range.m_nStartIndex = nMeshIndices;
for ( int f=0; f<nFaces; ++f )
{
uint32 i0 = pInputIndices[ f * 3 ];
uint32 i1 = pInputIndices[ f * 3 + 1 ];
uint32 i2 = pInputIndices[ f * 3 + 2 ];
Vector vCenter = *(Vector*)inputMesh.GetVertex( i0 );
vCenter += *(Vector*)inputMesh.GetVertex( i1 );
vCenter += *(Vector*)inputMesh.GetVertex( i2 );
vCenter /= 3.0f;
if ( vCenter.x > volume.m_vMinBounds.x && vCenter.x <= volume.m_vMaxBounds.x &&
vCenter.y > volume.m_vMinBounds.y && vCenter.y <= volume.m_vMaxBounds.y &&
vCenter.z > volume.m_vMinBounds.z && vCenter.z <= volume.m_vMaxBounds.z )
{
// Add the whole triangle
pOutputIndices[ nMeshIndices++ ] = i0;
pOutputIndices[ nMeshIndices++ ] = i1;
pOutputIndices[ nMeshIndices++ ] = i2;
}
}
range.m_nIndexCount = nMeshIndices - range.m_nStartIndex;
}
Assert( nMeshIndices == inputMesh.m_nIndexCount );
}

1178
meshutils/convexhull.cpp Normal file

File diff suppressed because it is too large Load Diff

1543
meshutils/mesh.cpp Normal file

File diff suppressed because it is too large Load Diff

46
meshutils/meshutils.vpc Normal file
View File

@@ -0,0 +1,46 @@
//-----------------------------------------------------------------------------
// MESHUTILS.VPC
//
// Project Script
//-----------------------------------------------------------------------------
$macro SRCDIR ".."
$include "$SRCDIR\vpc_scripts\source_lib_base.vpc"
$Configuration
{
$Compiler
{
$AdditionalIncludeDirectories "$BASE;..\public\meshutils"
}
}
$Project "meshutils"
{
$Folder "Source Files"
{
$File "atlas.cpp"
$File "clipmesh.cpp"
$File "convexhull.cpp"
$File "mesh.cpp"
$File "simplify.cpp"
$File "vertexops.cpp"
$File "uvparam.cpp"
}
$Folder "External Source Files"
{
$File "$SRCDIR\public\interpolatortypes.cpp"
}
$Folder "Public Header Files"
{
$File "$SRCDIR\public\meshutils\simplify.h"
$File "$SRCDIR\public\meshutils\mesh.h"
}
$Folder "Header Files"
{
}
}

View File

@@ -0,0 +1,13 @@
"vpc_cache"
{
"CacheVersion" "1"
"win32"
{
"CRCFile" "meshutils.vcxproj.vpc_crc"
"OutputFiles"
{
"0" "meshutils.vcxproj"
"1" "meshutils.vcxproj.filters"
}
}
}

815
meshutils/simplify.cpp Normal file
View File

@@ -0,0 +1,815 @@
//=========== Copyright <20> 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();
}

393
meshutils/uvparam.cpp Normal file
View File

@@ -0,0 +1,393 @@
//=========== Copyright <20> Valve Corporation, All rights reserved. ============//
//
// Purpose: Mesh class UV parameterization operations.
//
//===========================================================================//
#include "mesh.h"
#include "tier1/utlbuffer.h"
Vector4D PlaneFromTriangle( Vector &A, Vector &B, Vector &C )
{
// Calculate normal
Vector vAB = B - A;
Vector vAC = C - A;
Vector vNorm = -CrossProduct( vAC, vAB );
vNorm.NormalizeInPlace();
float d = DotProduct( A, vNorm );
return Vector4D( vNorm.x, vNorm.y, vNorm.z, d );
}
Vector4D CMesh::PlaneFromTriangle( int nTriangle ) const
{
Vector A = *(Vector*)GetVertex( m_pIndices[ nTriangle * 3 ] );
Vector B = *(Vector*)GetVertex( m_pIndices[ nTriangle * 3 + 1 ] );
Vector C = *(Vector*)GetVertex( m_pIndices[ nTriangle * 3 + 2 ] );
return ::PlaneFromTriangle( A, B, C );
}
int AddTriangleToChart( const CMesh &inputMesh, int nTriangle, UVChart_t *pChart, float flThreshold, CUtlVector<bool> &usedTriangles, int* pAdjacency )
{
if ( usedTriangles[ nTriangle ] == true )
return 0;
Vector4D vTriPlane = inputMesh.PlaneFromTriangle( nTriangle );
float flDot = DotProduct( vTriPlane.AsVector3D(), pChart->m_vPlane.AsVector3D() );
if ( flDot < flThreshold )
{
return 0;
}
// Add this triangle to the chart
pChart->m_TriangleList.AddToTail( nTriangle );
usedTriangles[ nTriangle ] = true;
int nAdded = 1;
// Add any adjacent triangles to the chart
for ( int i=0; i<3; ++i )
{
int nAdj = pAdjacency[ nTriangle * 3 + i ];
if ( nAdj != -1)
{
nAdded += AddTriangleToChart( inputMesh, nAdj, pChart, flThreshold, usedTriangles, pAdjacency );
}
}
return nAdded;
}
//--------------------------------------------------------------------------------------
// CreateUniqueUVParameterization
//
// Creates a unique parameterization for the mesh in 0..1 UV space. The mesh is assumed
// to be welded and clean before this is called.
//--------------------------------------------------------------------------------------
bool CreateUniqueUVParameterization( CMesh *pMeshOut, const CMesh &inputMesh, float flThreshold, int nAtlasTextureSizeX, int nAtlasTextureSizeY, float flGutterSize )
{
int nMaxCharts = 10000;
// Generate adjacency
int *pAdjacencyBuffer = new int[ inputMesh.m_nIndexCount ];
if ( !inputMesh.CalculateAdjacency( pAdjacencyBuffer, inputMesh.m_nIndexCount ) )
{
return false;
}
int nPosOffset = inputMesh.FindFirstAttributeOffset( VERTEX_ELEMENT_POSITION );
int nTexOffset = inputMesh.FindFirstAttributeOffset( VERTEX_ELEMENT_TEXCOORD2D_0 );
if ( nTexOffset == -1 )
{
nTexOffset = inputMesh.FindFirstAttributeOffset( VERTEX_ELEMENT_TEXCOORD3D_0 );
}
if ( nTexOffset == -1 || nPosOffset == -1)
{
Warning( "Cannot create UV parameterization without position or texcoords!\n" );
return false;
}
// Now go through the triangles and add them to charts based upon minimum angle between charts
CUtlVector<int> triangleIndices;
CUtlVector<bool> usedTriangles;
CUtlVector<UVChart_t*> chartList;
int nTris = inputMesh.m_nIndexCount / 3;
triangleIndices.EnsureCount( nTris );
usedTriangles.EnsureCount( nTris );
for( int t=0; t<nTris; ++t )
{
triangleIndices[t] = t;
usedTriangles[t] = false;
}
bool *pUsedTriangles = usedTriangles.Base();
int nRemaining = nTris;
while( nRemaining > 0 )
{
// Select a random triangle
int nTriangle = -1;
for( int t=0; t<nTris; ++t )
{
if ( pUsedTriangles[t] == false )
{
nTriangle = t;
break;
}
}
if ( nTriangle > -1 )
{
Vector4D vTriPlane = inputMesh.PlaneFromTriangle( nTriangle );
if ( vTriPlane.AsVector3D().LengthSqr() < 0.9f )
{
// degenerate tri, just get rid of it
pUsedTriangles[ nTriangle ] = true;
nRemaining --;
}
else
{
// create a new chart
UVChart_t *pNewChart = new UVChart_t;
pNewChart->m_vPlane = vTriPlane;
pNewChart->m_vMinUV = Vector2D( FLT_MAX, FLT_MAX );
pNewChart->m_vMaxUV = Vector2D( -FLT_MAX, -FLT_MAX );
int nAdded = AddTriangleToChart( inputMesh, nTriangle, pNewChart, flThreshold, usedTriangles, pAdjacencyBuffer );
if ( nAdded < 1 )
{
Msg( "Error: didn't add any triangles to chart: %d\n", nTriangle );
}
nRemaining -= nAdded;
// Add the chart to the list
chartList.AddToTail( pNewChart );
if ( chartList.Count() > nMaxCharts )
{
nRemaining = 0;
break;
}
}
}
else
{
Assert( nRemaining == 0 );
nRemaining = 0;
}
}
delete []pAdjacencyBuffer;
pAdjacencyBuffer = NULL;
// create a local texture vector
CUtlVector<AtlasChart_t> atlasChartVector;
CMesh tempMesh;
int nTotalChartTriangles = 0;
int nCharts = chartList.Count();
float flTotalArea = 0;
if ( nCharts < nMaxCharts )
{
// Average each chart's plane and create a new texture for each chart
int nCharts = chartList.Count();
atlasChartVector.EnsureCount( nCharts );
for ( int c=0; c<nCharts; ++c )
{
UVChart_t *pChart = chartList[c];
int nTris = pChart->m_TriangleList.Count();
Vector4D vPlane(0,0,0,0);
for ( int p=0; p<nTris; ++p )
{
int iTri = pChart->m_TriangleList[p];
vPlane += inputMesh.PlaneFromTriangle( iTri );
}
nTotalChartTriangles += nTris;
Vector vNorm = vPlane.AsVector3D().Normalized();
pChart->m_vPlane = Vector4D( vNorm.x, vNorm.y, vNorm.z, 0 );
AtlasChart_t AtlasChart;
AtlasChart.m_bAtlased = false;
AtlasChart.m_vAtlasMin.Init( 0, 0 );
AtlasChart.m_vAtlasMax.Init( 0, 0 );
AtlasChart.m_vMaxTextureSize.Init( 0, 0 );
atlasChartVector[ c ] = AtlasChart;
}
// Create a new temporary mesh
tempMesh.AllocateMesh( nTotalChartTriangles * 3, nTotalChartTriangles * 3, inputMesh.m_nVertexStrideFloats, inputMesh.m_pAttributes, inputMesh.m_nAttributeCount );
// loop through all charts and add the vertices and indices to the new mesh
int nNewVertex = 0;
flTotalArea = 0.0f;
for ( int c=0; c<nCharts; ++c )
{
UVChart_t *pChart = chartList[c];
Vector vNorm = pChart->m_vPlane.AsVector3D();
Vector vUp(0,1,0);
if ( DotProduct( vNorm, vUp ) > 0.95f )
vUp = Vector(0,0,1);
Vector vRight = CrossProduct( vNorm, vUp );
vRight.NormalizeInPlace();
vUp = CrossProduct( vRight, vNorm );
vUp.NormalizeInPlace();
pChart->m_nVertexStart = nNewVertex;
// project the vertices onto the chart plane
// and find the min and max plane boundaries
int nTris = pChart->m_TriangleList.Count();
for ( int t=0; t<nTris; ++t )
{
int iTri = pChart->m_TriangleList[t];
for ( int i=0; i<3; ++i )
{
int nIndex = inputMesh.m_pIndices[ iTri * 3 + i ];
float *pVert = (float*)inputMesh.GetVertex( nIndex );
Vector &vPos = *( ( Vector* )( pVert + nPosOffset ) );
Vector2D vTexcoord;
vTexcoord.x = DotProduct( vPos, vRight );
vTexcoord.y = DotProduct( vPos, vUp );
pChart->m_vMinUV.x = MIN( vTexcoord.x, pChart->m_vMinUV.x );
pChart->m_vMinUV.y = MIN( vTexcoord.y, pChart->m_vMinUV.y );
pChart->m_vMaxUV.x = MAX( vTexcoord.x, pChart->m_vMaxUV.x );
pChart->m_vMaxUV.y = MAX( vTexcoord.y, pChart->m_vMaxUV.y );
float *pNewVert = tempMesh.GetVertex( nNewVertex );
// New vertex
CopyVertex( pNewVert, pVert, inputMesh.m_nVertexStrideFloats );
Vector2D *pNewTex = (Vector2D*)( pNewVert + nTexOffset );
*pNewTex = vTexcoord;
// New index
tempMesh.m_pIndices[ nNewVertex ] = nNewVertex;
nNewVertex++;
}
}
pChart->m_nVertexCount = nNewVertex - pChart->m_nVertexStart;
// update size of the texture
AtlasChart_t &chart = atlasChartVector[ c ];
chart.m_vMaxTextureSize.x = pChart->m_vMaxUV.x - pChart->m_vMinUV.x;
chart.m_vMaxTextureSize.y = pChart->m_vMaxUV.y - pChart->m_vMinUV.y;
flTotalArea += chart.m_vMaxTextureSize.x * chart.m_vMaxTextureSize.y;
Vector2D vChartUVDelta = pChart->m_vMaxUV - pChart->m_vMinUV;
// Normalize texture coordinates within the chart plane boundaries
if ( vChartUVDelta.x == 0 || vChartUVDelta.y == 0 )
{
// Zero texcoords if our chart is infintesimally small
for ( int v=pChart->m_nVertexStart; v<pChart->m_nVertexStart + pChart->m_nVertexCount; ++v )
{
float *pNewVert = tempMesh.GetVertex( v );
Vector2D *pNewTex = (Vector2D*)( pNewVert + nTexOffset );
*pNewTex = Vector2D(0,0);
}
}
else
{
for ( int v=pChart->m_nVertexStart; v<pChart->m_nVertexStart + pChart->m_nVertexCount; ++v )
{
float *pNewVert = tempMesh.GetVertex( v );
Vector2D *pNewTex = (Vector2D*)( pNewVert + nTexOffset );
Vector2D vNewTex = ( *pNewTex - pChart->m_vMinUV ) / vChartUVDelta;
*pNewTex = vNewTex;
}
}
}
}
bool bMadeAtlas = false;
if ( nCharts < nMaxCharts )
{
// We made a chart
bMadeAtlas = true;
// Create an atlas
Msg( "Attempting to atlas %d charts\n", nCharts );
int nAtlasSideSize = (int)(sqrtf( flTotalArea ));
int nGrowAmount = MAX( 8, nAtlasSideSize / 64 );
nAtlasSideSize -= nGrowAmount * 2;
PackChartsIntoAtlas( atlasChartVector.Base(), atlasChartVector.Count(), nAtlasSideSize, nAtlasSideSize, nGrowAmount );
Vector2D vTextureSize( nAtlasTextureSizeX, nAtlasTextureSizeY );
Vector2D vGutterOffset( flGutterSize / vTextureSize.x, flGutterSize / vTextureSize.y );
// Update triangle coordinates to fit into this atlas
for ( int c=0; c<nCharts; ++c )
{
UVChart_t *pChart = chartList[ c ];
AtlasChart_t &atlasData = atlasChartVector[ c ];
Vector2D vAtlasMin = ( atlasData.m_vAtlasMin ) + vGutterOffset;
Vector2D vAtlasMax = ( atlasData.m_vAtlasMax ) - vGutterOffset;
Vector2D vDeltaAtlas = vAtlasMax - vAtlasMin;
Vector2D vDeltaBounds(1,1);
Vector2D vAtlasUVSize(0,0);
if ( vDeltaBounds.x != 0.0f && vDeltaBounds.y != 0.0f )
vAtlasUVSize = vDeltaAtlas / vDeltaBounds;
Vector2D vShift = vAtlasMin - Vector2D(0,0) * vAtlasUVSize;
for ( int v=pChart->m_nVertexStart; v<pChart->m_nVertexStart + pChart->m_nVertexCount; ++v )
{
float *pNewVert = tempMesh.GetVertex( v );
Vector2D *pNewTex = (Vector2D*)( pNewVert + nTexOffset );
pNewTex->x = pNewTex->x * vAtlasUVSize.x + vShift.x;
pNewTex->y = pNewTex->y * vAtlasUVSize.y + vShift.y;
}
}
// Clean and weld the mesh
float flEpsilon = 1e-6;
float *pEpsilons = new float[ tempMesh.m_nVertexStrideFloats ];
for ( int e=0; e<tempMesh.m_nVertexStrideFloats; ++e )
{
pEpsilons[ e ] = flEpsilon;
}
WeldVertices( pMeshOut, tempMesh, pEpsilons, tempMesh.m_nVertexStrideFloats );
delete []pEpsilons;
}
else
{
// We didn't make a chart
bMadeAtlas = false;
// Create an atlas
Msg( "Too many charts (%d), creating planar mapping\n", nCharts );
// Create a planar projection
Vector vMinBounds;
Vector vMaxBounds;
inputMesh.CalculateBounds( &vMinBounds, &vMaxBounds );
// BBox delta
Vector vBoundsDelta = vMaxBounds - vMinBounds;
DuplicateMesh( pMeshOut, inputMesh );
// Update UVs based on... shakes magic 8 ball... XZ projection, for now
for ( int v=0; v<pMeshOut->m_nVertexCount; ++v )
{
float *pNewVert = pMeshOut->GetVertex( v );
Vector *pPos = ( Vector* )( pNewVert + nPosOffset );
Vector2D *pNewTex = ( Vector2D* )( pNewVert + nTexOffset );
pNewTex->x = ( pPos->x - vMinBounds.x ) / vBoundsDelta.x;
pNewTex->y = ( pPos->z - vMinBounds.z ) / vBoundsDelta.z;
}
}
// Delete the charts
nCharts = chartList.Count();
for ( int c=0; c<nCharts; ++c )
{
UVChart_t *pChart = chartList[c];
delete pChart;
}
chartList.Purge();
return bMadeAtlas;
}

67
meshutils/vertexops.cpp Normal file
View File

@@ -0,0 +1,67 @@
//=========== Copyright <20> Valve Corporation, All rights reserved. ============//
//
// Purpose: Mesh clipping operations.
//
//===========================================================================//
#include "mesh.h"
void CopyVertex( float *pOut, const float *pIn, int nFloats )
{
Q_memcpy( pOut, pIn, nFloats * sizeof( float ) );
}
void SubtractVertex( float *pOutput, const float *pLeft, const float *pRight, int nFloats )
{
for ( int f=0; f<nFloats; ++f )
{
pOutput[f] = pLeft[f] - pRight[f];
}
}
void AddVertex( float *pOutput, const float *pLeft,const float *pRight, int nFloats )
{
for ( int f=0; f<nFloats; ++f )
{
pOutput[f] = pLeft[f] + pRight[f];
}
}
void MultiplyVertex( float *pOutput, const float *pLeft, const float* pRight, float nFloats )
{
for ( int f=0; f<nFloats; ++f )
{
pOutput[f] = pLeft[f] * pRight[f];
}
}
void AddVertexInPlace( float *pLeft, const float *pRight, int nFloats )
{
for ( int f=0; f<nFloats; ++f )
{
pLeft[f] += pRight[f];
}
}
void MultiplyVertexInPlace( float *pLeft, const float flRight, int nFloats )
{
for ( int f=0; f<nFloats; ++f )
{
pLeft[f] *= flRight;
}
}
void LerpVertex( float *pOutput, const float *pLeft, const float *pRight, float flLerp, int nFloats )
{
for ( int f=0; f<nFloats; ++f )
{
pOutput[f] = pLeft[f] + flLerp * ( pRight[f] - pLeft[f] );
}
}
void BaryCentricVertices( float *pOutput, float *p0, float *p1, float *p2, float flU, float flV, float flW, int nFloats )
{
for ( int f=0; f<nFloats; ++f )
{
pOutput[f] = p0[f] * flU + p1[f] * flV + p2[f] * flW;
}
}

2
meshutils/vsi.nul Normal file
View File

@@ -0,0 +1,2 @@
SN Visual Studio Integration
IMPORTANT: Do not remove the custom build step for this file