initial
This commit is contained in:
58
meshutils/_vpc_/manifest_meshutils/win32/manifest.txt
Normal file
58
meshutils/_vpc_/manifest_meshutils/win32/manifest.txt
Normal 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
274
meshutils/atlas.cpp
Normal 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
274
meshutils/clipmesh.cpp
Normal 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
1178
meshutils/convexhull.cpp
Normal file
File diff suppressed because it is too large
Load Diff
1543
meshutils/mesh.cpp
Normal file
1543
meshutils/mesh.cpp
Normal file
File diff suppressed because it is too large
Load Diff
46
meshutils/meshutils.vpc
Normal file
46
meshutils/meshutils.vpc
Normal 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"
|
||||
{
|
||||
}
|
||||
}
|
||||
13
meshutils/meshutils.vpc.vpc_cache
Normal file
13
meshutils/meshutils.vpc.vpc_cache
Normal 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
815
meshutils/simplify.cpp
Normal 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 ¶ms, 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 ¶ms, const mesh_simplifyweights_t *pWeights )
|
||||
{
|
||||
Vprof_Start_IfEnabled();
|
||||
SimplifyMeshQEM2( meshOut, input, params, pWeights);
|
||||
|
||||
Vprof_Report_IfEnabled();
|
||||
}
|
||||
|
||||
393
meshutils/uvparam.cpp
Normal file
393
meshutils/uvparam.cpp
Normal 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
67
meshutils/vertexops.cpp
Normal 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
2
meshutils/vsi.nul
Normal file
@@ -0,0 +1,2 @@
|
||||
SN Visual Studio Integration
|
||||
IMPORTANT: Do not remove the custom build step for this file
|
||||
Reference in New Issue
Block a user