|
|
//================ Copyright (c) Valve Corporation. All Rights Reserved. ===========================
//
//
//
//==================================================================================================
#include "mesh.h"
#include "tier1/mempool.h"
#include "utlintrusivelist.h"
#include "mathlib/ssemath.h"
class CHullTri; static int g_Mod3Table[] = { 0, 1, 2, 0, 1, 2 };
int SupportPoint( const Vector &vDirection, const float *pVerts, int nVertCount, uint32 nVertexStrideFloats ) { if ( nVertCount < 2 ) return 0; const float *pDirection = vDirection.Base(); float flMax = DotProduct( pDirection, pVerts ); int nMax = 0; pVerts += nVertexStrideFloats; for ( int i = 1; i < nVertCount; i++ ) { float flDot = DotProduct( pDirection, pVerts ); if ( flDot > flMax ) { flMax = flDot; nMax = i; } pVerts += nVertexStrideFloats; }
return nMax; }
// Class half-edge data structure. Stores neighboring information per triangle edge
// so it is possible to walk adjacent parts of the mesh
class CHullHalfEdge { public: CHullHalfEdge() { m_pTri = NULL; m_pNext = NULL; m_pPrev = NULL; m_pOpposite = NULL; } void SetNext( CHullHalfEdge *pNext ) { pNext->m_pPrev = this; m_pNext = pNext; } void GetVerts( int nVerts[2] ); int GetStartVert(); int GetIndex();
CHullTri *m_pTri; // triangle containing these edges
CHullHalfEdge *m_pNext; // next edge on this tri in clockwise order
CHullHalfEdge *m_pPrev; // previous edge
CHullHalfEdge *m_pOpposite; // the other half edge on the other triangle that shares this edge
};
// each triangle on the hull or potentially on the hull is one of these
class CHullTri { public: // compute and store the plane of this triangle for later computation
void Init( int nV0, int nV1, int nV2, const float *pVerts, int nVertexStrideFloats ) { // save vertex indices
m_nIndex[0] = nV0; m_nIndex[1] = nV1; m_nIndex[2] = nV2;
// compute plane normal and plane constant
Vector vEdge0 = *(Vector *)(pVerts + nV1 * nVertexStrideFloats ) - *(Vector *)(pVerts + nV0 * nVertexStrideFloats ); Vector vEdge1 = *(Vector *)(pVerts + nV2 * nVertexStrideFloats ) - *(Vector *)(pVerts + nV0 * nVertexStrideFloats ); m_vNormal = CrossProduct( vEdge1, vEdge0 ); VectorNormalize( m_vNormal ); m_flDist = DotProduct( pVerts + nV0 * nVertexStrideFloats, m_vNormal.Base() );
// Part of the list of vertices in front of the plane
m_flMaxDist = 0; m_pMaxVert = NULL; // mailbox for mesh walking
m_nVisitCount = 0;
// setup half edges
for ( int i = 0; i < 3; i++ ) { m_edges[i].m_pTri = this; m_edges[i].SetNext( &m_edges[g_Mod3Table[(i+1)]] ); } }
// distance from a vert to the triangle's plane
float VertDist( const float *pVert ) { float flDist = DotProduct( pVert, m_vNormal.Base() ) - m_flDist; return flDist; }
// link half edges to neighboring triangles
// NOTE: does the backlinks too, which results in doing that work twice, optimize?
void SetNeighbors( CHullHalfEdge *p0, CHullHalfEdge *p1, CHullHalfEdge *p2 ) { m_edges[0].m_pOpposite = p0; p0->m_pOpposite = &m_edges[0];
m_edges[1].m_pOpposite = p1; p1->m_pOpposite = &m_edges[1];
m_edges[2].m_pOpposite = p2; p2->m_pOpposite = &m_edges[2]; }
// debug, check validity / consistency. I used this to find bugs when relinking the mesh
bool IsValid() { if ( m_edges[0].m_pNext != &m_edges[1] || m_edges[0].m_pPrev != &m_edges[2] || m_edges[1].m_pNext != &m_edges[2] || m_edges[1].m_pPrev != &m_edges[0] || m_edges[2].m_pNext != &m_edges[0] || m_edges[2].m_pPrev != &m_edges[1] ) return false; if ( m_edges[0].m_pTri != this || m_edges[1].m_pTri != this || m_edges[2].m_pTri != this ) return false; if ( m_edges[0].m_pOpposite->m_pOpposite != &m_edges[0] || m_edges[1].m_pOpposite->m_pOpposite != &m_edges[1] || m_edges[2].m_pOpposite->m_pOpposite != &m_edges[2] ) return false; return true; }
// simple accessor
inline CHullHalfEdge *GetEdge( int nIndex ) { return &m_edges[nIndex]; }
CUtlVector<const float *> m_pVerts; CHullHalfEdge m_edges[3]; Vector m_vNormal; float m_flDist; int m_nIndex[3]; int m_nVisitCount; const float *m_pMaxVert; float m_flMaxDist; CHullTri *m_pNext; CHullTri *m_pPrev; };
// gets the verts from an edge
void CHullHalfEdge::GetVerts( int nVerts[2] ) { int nIndex = this - m_pTri->m_edges; Assert(nIndex>=0 && nIndex<ARRAYSIZE(m_pTri->m_edges)); nVerts[0] = m_pTri->m_nIndex[nIndex]; nVerts[1] = m_pTri->m_nIndex[ g_Mod3Table[nIndex+1] ]; }
// edge to edge index (0,1,2)
int CHullHalfEdge::GetIndex() { int nIndex = this - m_pTri->m_edges; Assert(nIndex>=0 && nIndex<ARRAYSIZE(m_pTri->m_edges)); return nIndex; }
int CHullHalfEdge::GetStartVert() { return m_pTri->m_nIndex[GetIndex()]; }
// instantiate one of these to build convex hulls and fit OBBs
class CConvexHullBuilder { public: CConvexHullBuilder( const float *pVerts, int nVertexCount, uint32 nVertexStrideFloats, float flFrontEpsilon ) : m_triPool(128), m_nCurrentVisit(0), m_pVerts(pVerts), m_nVertexStrideFloats(nVertexStrideFloats), m_nVertexCount(nVertexCount), m_flCoplanarEpsilon(flFrontEpsilon) {}
void BuildHull(); // tries all faces
void FitOBB( matrix3x4_t &xform, Vector &vExtents ); // tries only the face with minimum extents of hull along normal
void FitOBBFast( matrix3x4_t &xform, Vector &vExtents ); void GenerateOutputMesh( CMesh *pOutputMesh );
bool IsValid() { for ( CHullTri *pTri = m_faceListVerts.Head(); pTri != NULL; pTri = pTri->m_pNext ) { if ( !pTri->IsValid() ) return false; if ( pTri->m_pNext && pTri->m_pNext->m_pPrev != pTri ) return false; if ( pTri->m_pPrev && pTri->m_pPrev->m_pNext != pTri ) return false; if ( pTri->m_pMaxVert == NULL || pTri->m_pVerts.Count() <= 0 ) return false; } for ( CHullTri *pTri = m_faceListNoVerts.Head(); pTri != NULL; pTri = pTri->m_pNext ) { if ( !pTri->IsValid() ) return false; if ( pTri->m_pNext && pTri->m_pNext->m_pPrev != pTri ) return false; if ( pTri->m_pPrev && pTri->m_pPrev->m_pNext != pTri ) return false; if ( pTri->m_pMaxVert != NULL || pTri->m_pVerts.Count() > 0 ) return false; } return true; }
private: void BuildInitialTetrahedron(); void BuildHorizonList( CHullTri *pTri, const float *pVert ); void BuildHorizonList_r( CHullTri *pTri, const float *pVert, int nEdgeStart ); void BuildSilhouette_r( CHullTri *pTri, const Vector &vNormal, int nEdgeStart ); void BuildSilhouette( CHullTri *pTri, const Vector &vNormal ); void TransferVerts( CHullTri *pRemove, CHullTri *pNewTri ); float SupportExtents( const Vector &vDirection, float *pMin, float *pMax ) const; float SupportExtents_Silhouette( const Vector &vDirection, float *pMin = NULL, float *pMax = NULL ); void FitOBBToFace( CHullTri *pTri, matrix3x4_t &xform, Vector &vExtents );
inline const float *GetVertex( int nIndex ) { return m_pVerts + nIndex * m_nVertexStrideFloats; } inline const Vector *GetVertexPosition( int nIndex ) { return (const Vector *)(m_pVerts + nIndex * m_nVertexStrideFloats); } void NextVisit() { m_nCurrentVisit++; }
void AddTriangle( CHullTri *pTri ) { if ( pTri->m_pMaxVert ) { // add the new faces to the tail since they are likely to have fewer verts in front than faces already in the list
m_faceListVerts.AddToTail( pTri ); } else { m_faceListNoVerts.AddToHead( pTri ); } }
void RemoveTriangle( CHullTri *pTri ) { if ( pTri->m_pMaxVert ) { m_faceListVerts.RemoveNode( pTri ); } else { m_faceListNoVerts.RemoveNode( pTri ); } }
bool Visit( CHullTri *pTri ) { if ( pTri->m_nVisitCount != m_nCurrentVisit ) { pTri->m_nVisitCount = m_nCurrentVisit; return true; } return false; }
CUtlIntrusiveDListWithTailPtr<CHullTri> m_faceListVerts; CUtlIntrusiveDList<CHullTri> m_faceListNoVerts;
CClassMemoryPool<CHullTri> m_triPool; CUtlVectorFixedGrowable<CHullHalfEdge *, 256> m_halfEdgeList; CUtlVectorFixedGrowable<CHullTri *, 256> m_removeList; CUtlVectorFixedGrowable<const float *, 256> m_silhouetteVertexList; const float *m_pVerts; float m_flCoplanarEpsilon; uint32 m_nVertexStrideFloats; int m_nVertexCount; int m_nCurrentVisit; };
// Builds a tetradhedron to start the convex hull
// This does an iteration like GJK - could use bounding box or some other method
// BUGBUG: Test this with 2D mesh for failure cases
void CConvexHullBuilder::BuildInitialTetrahedron() { Vector vDir(0,0,1); int i0 = SupportPoint( vDir, m_pVerts, m_nVertexCount, m_nVertexStrideFloats ); int i1 = SupportPoint( -vDir, m_pVerts, m_nVertexCount, m_nVertexStrideFloats ); Vector vEdge0 = *GetVertexPosition(i1) - *GetVertexPosition(i0); Vector vNormal = CrossProduct( *GetVertexPosition(i0), vEdge0 ); if ( vNormal.LengthSqr() < 1e-3f ) { vNormal.x = 1.0f; } int i2 = SupportPoint( vNormal, m_pVerts, m_nVertexCount, m_nVertexStrideFloats ); if ( i2 == i0 || i2 == i1 ) { i2 = SupportPoint( -vNormal, m_pVerts, m_nVertexCount, m_nVertexStrideFloats ); }
Vector vEdge1 = *GetVertexPosition(i2) - *GetVertexPosition(i0); vNormal = CrossProduct( vEdge1, vEdge0 ); int i3 = SupportPoint( -vNormal, m_pVerts, m_nVertexCount, m_nVertexStrideFloats );
CHullTri *pTri0 = m_triPool.Alloc(); pTri0->Init( i0, i1, i2, m_pVerts, m_nVertexStrideFloats );
CHullTri *pTri1 = m_triPool.Alloc(); pTri1->Init( i0, i3, i1, m_pVerts, m_nVertexStrideFloats );
CHullTri *pTri2 = m_triPool.Alloc(); pTri2->Init( i1, i3, i2, m_pVerts, m_nVertexStrideFloats );
CHullTri *pTri3 = m_triPool.Alloc(); pTri3->Init( i2, i3, i0, m_pVerts, m_nVertexStrideFloats );
m_faceListVerts.AddToTail(pTri0); m_faceListVerts.AddToTail(pTri1); m_faceListVerts.AddToTail(pTri2); m_faceListVerts.AddToTail(pTri3);
pTri0->SetNeighbors( pTri1->GetEdge(2), pTri2->GetEdge(2), pTri3->GetEdge(2) ); pTri1->SetNeighbors( pTri3->GetEdge(1), pTri2->GetEdge(0), pTri0->GetEdge(0) ); pTri2->SetNeighbors( pTri1->GetEdge(1), pTri3->GetEdge(0), pTri0->GetEdge(1) ); pTri3->SetNeighbors( pTri2->GetEdge(1), pTri1->GetEdge(0), pTri0->GetEdge(2) ); const float flFrontDist = m_flCoplanarEpsilon; for ( int i = 0; i < m_nVertexCount; i++ ) { for ( CHullTri *pTri = m_faceListVerts.Head(); pTri != NULL; pTri = pTri->m_pNext ) { const float *pVert = GetVertex(i); float flDist = pTri->VertDist( pVert );
if ( flDist > flFrontDist ) { pTri->m_pVerts.AddToTail( pVert ); if ( flDist > pTri->m_flMaxDist ) { pTri->m_flMaxDist = flDist; pTri->m_pMaxVert = pVert; } break; } } }
for ( CHullTri *pNext = NULL, *pTri = m_faceListVerts.Head(); pTri != NULL; pTri = pNext ) { pNext = pTri->m_pNext; if ( !pTri->m_pMaxVert ) { m_faceListVerts.RemoveNode( pTri ); m_faceListNoVerts.AddToTail( pTri ); } } }
// Recursively visit neighboring triangles to produce the list of edges that are silhouettes with respect
// to the new vertex.
// IMPORTANT NOTE: This recursion is carefully designed to visit the boundary edge list exactly in order
// Once it reaches the boundary it will form an edge loop in order in the m_halfEdgeList array
// If this array is not in order the half-edges will not be linked up properly in BuildHull
void CConvexHullBuilder::BuildHorizonList_r( CHullTri *pTri, const float *pVert, int nEdgeStart ) { RemoveTriangle( pTri ); for ( int i = 0; i < 3; i++ ) { // this visits the neighbors in clockwise order starting with the edge you came in on
CHullHalfEdge *pNeighborEdge = pTri->GetEdge( g_Mod3Table[( i + nEdgeStart)])->m_pOpposite; CHullTri *pNeighbor = pNeighborEdge->m_pTri; bool bRecurse = Visit( pNeighbor ); if ( pNeighbor->VertDist(pVert) > 0 ) { if ( bRecurse ) { BuildHorizonList_r( pNeighbor, pVert, pNeighborEdge->GetIndex() ); } } else { // This edge has a neighbor that has the new vert in front (so removed) but it's triangle
// is not in front. This is a silhouette edge that will remain on the hull, but needs to
// be relinked to a new triangle
m_halfEdgeList.AddToTail( pNeighborEdge ); } } m_removeList.AddToTail( pTri ); }
// Starts a new recursion to found the silhouette edge loop
void CConvexHullBuilder::BuildHorizonList( CHullTri *pTri, const float *pVert ) { // clear out any list - we're going to write this
m_halfEdgeList.RemoveAll(); // advance the token we use to avoid visiting triangles twice
NextVisit(); // mark the starting triangle as visited so we don't re-enter it
Visit( pTri ); BuildHorizonList_r( pTri, pVert, 0 ); }
void CConvexHullBuilder::BuildSilhouette_r( CHullTri *pTri, const Vector &vNormal, int nEdgeStart ) { for ( int i = 0; i < 3; i++ ) { // this visits the neighbors in clockwise order starting with the edge you came in on
CHullHalfEdge *pNeighborEdge = pTri->GetEdge( g_Mod3Table[( i + nEdgeStart)])->m_pOpposite; CHullTri *pNeighbor = pNeighborEdge->m_pTri; bool bRecurse = Visit( pNeighbor ); float flDot = DotProduct( vNormal, pNeighbor->m_vNormal ); if ( flDot > 0 ) { if ( bRecurse ) { BuildSilhouette_r( pNeighbor, vNormal, pNeighborEdge->GetIndex() ); } } else { // add edge to sil
m_silhouetteVertexList.AddToTail( GetVertex( pNeighborEdge->m_pNext->GetStartVert() ) ); } } }
// Starts a new recursion to found the silhouette edge loop
void CConvexHullBuilder::BuildSilhouette( CHullTri *pTri, const Vector &vNormal ) { // clear out any list - we're going to write this
m_silhouetteVertexList.RemoveAll(); // advance the token we use to avoid visiting triangles twice
NextVisit(); // mark the starting triangle as visited so we don't re-enter it
Visit( pTri ); BuildSilhouette_r( pTri, vNormal, 0 ); }
// transfer any verts in the front list of pRemove to the front list of pNewTri if they
// are in front of pNewTri
// Remove them from the original list so they won't get transferred to subsequent triangles
void CConvexHullBuilder::TransferVerts( CHullTri *pRemove, CHullTri *pNewTri ) { int nVertex = 0; const float flFrontDist = m_flCoplanarEpsilon; while ( nVertex < pRemove->m_pVerts.Count() ) { const float *pCheckVert = pRemove->m_pVerts[nVertex]; if ( pCheckVert == pRemove->m_pMaxVert ) { pRemove->m_pVerts.FastRemove(nVertex); continue; } float flDist = pNewTri->VertDist(pCheckVert); if ( flDist > flFrontDist ) { pNewTri->m_pVerts.AddToTail( pCheckVert ); if ( flDist > pNewTri->m_flMaxDist ) { pNewTri->m_flMaxDist = flDist; pNewTri->m_pMaxVert = pCheckVert; } pRemove->m_pVerts.FastRemove(nVertex); } else { nVertex++; } } }
float CConvexHullBuilder::SupportExtents( const Vector &vDirection, float *pMin, float *pMax ) const { const float *pVerts = m_pVerts; const float *pDirection = vDirection.Base(); float flMax = DotProduct( pDirection, pVerts ); float flMin = flMax; const uint32 nStride = m_nVertexStrideFloats; pVerts += nStride; for ( int i = 1; i < m_nVertexCount; i++ ) { float flDot = DotProduct( pDirection, pVerts ); flMax = MAX(flMax, flDot); flMin = MIN(flMin, flDot); pVerts += nStride; }
if ( pMin ) { *pMin = flMin; } if ( pMax ) { *pMax = flMax; } return flMax - flMin; }
// UNDONE: Only look at half edge verts
float CConvexHullBuilder::SupportExtents_Silhouette( const Vector &vDirection, float *pMin, float *pMax ) { if ( !m_silhouetteVertexList.Count() ) return 0;
const float *pDirection = vDirection.Base(); float flMax = DotProduct( pDirection, m_silhouetteVertexList[0] ); float flMin = flMax; int nVertCount = m_silhouetteVertexList.Count(); for ( int i = 1; i < nVertCount; i++ ) { float flDot = DotProduct( pDirection, m_silhouetteVertexList[i] ); flMax = MAX(flMax, flDot); flMin = MIN(flMin, flDot); } if ( pMin ) { *pMin = flMin; } if ( pMax ) { *pMax = flMax; }
return flMax - flMin; }
// Construct the triangulated convex hull from the point set
void CConvexHullBuilder::BuildHull() { // get a tetrahedron for this mesh and build a list of verts for each face
BuildInitialTetrahedron();
// process each face that has vertices in front of it until all are done
while ( m_faceListVerts.Head() != NULL ) { CHullTri *pTri = m_faceListVerts.Head();
const float *pNewVert = pTri->m_pMaxVert; int nNewVertIndex = (pNewVert - m_pVerts) / m_nVertexStrideFloats; // Get the horizon edge loop of the new vert
BuildHorizonList( pTri, pNewVert );
// now build a new triangle along each horizon edge and the new vert
int nEdgeVerts[2]; CUtlVectorFixedGrowable<CHullTri *, 64> addedList; int nLastEdgeVert = -1; for ( int i = 0; i < m_halfEdgeList.Count(); i++ ) { CHullHalfEdge *pEdge = m_halfEdgeList[i]; pEdge->GetVerts( nEdgeVerts ); CHullTri *pNewTri = m_triPool.Alloc(); if ( nLastEdgeVert >= 0 ) { Assert(nEdgeVerts[1]==nLastEdgeVert); } nLastEdgeVert = nEdgeVerts[0]; pNewTri->Init( nNewVertIndex, nEdgeVerts[1], nEdgeVerts[0], m_pVerts, m_nVertexStrideFloats );
// now transfer any verts in front of the new triangle
for ( int j = 0; j < m_removeList.Count(); j++ ) { TransferVerts( m_removeList[j], pNewTri ); } addedList.AddToTail( pNewTri );
// put this triangle in the list of hull triangles or the list of triangles to be expanded
AddTriangle(pNewTri); }
// now free the removed triangles (we had to keep them until their verts were transferred)
for ( int j = 0; j < m_removeList.Count(); j++ ) { m_triPool.Free( m_removeList[j] ); } m_removeList.RemoveAll();
// now link up the halfEdges between the new triangles and the hull mesh
// NOTE: This depends on m_halfEdgeList being an in-order loop which is guaranteed by
// the recursion order in BuildHorizon
int nNewTriangleCount = addedList.Count(); CHullTri *pPrev = addedList[ nNewTriangleCount-1 ]; for ( int i = 0; i < nNewTriangleCount; i++ ) { CHullTri *pNext = addedList[ (i + 1) % nNewTriangleCount ]; addedList[i]->SetNeighbors( pPrev->GetEdge(2), m_halfEdgeList[i], pNext->GetEdge(0) ); pPrev = addedList[i]; } }
Assert(m_faceListVerts.Head()==NULL); }
// write all of the faces on the hull to an output mesh
void CConvexHullBuilder::GenerateOutputMesh( CMesh *pOutMesh ) { CUtlVector<int> vertexMap; vertexMap.SetCount( m_nVertexCount ); vertexMap.FillWithValue( -1 ); int nOutVert = 0; int nOutTri = 0; for ( CHullTri *pTri = m_faceListNoVerts.Head(); pTri != NULL; pTri = pTri->m_pNext ) { for ( int j = 0; j < 3; j++ ) { int nIndex = pTri->m_nIndex[j]; if ( vertexMap[nIndex] < 0 ) { vertexMap[nIndex] = nOutVert++; } } nOutTri++; } vertexMap.FillWithValue(-1); pOutMesh->AllocateMesh( nOutVert, nOutTri * 3, m_nVertexStrideFloats, NULL, 0 ); nOutVert = 0; nOutTri = 0; for ( CHullTri *pTri = m_faceListNoVerts.Head(); pTri != NULL; pTri = pTri->m_pNext ) { for ( int j = 0; j < 3; j++ ) { int nIndex = pTri->m_nIndex[j]; if ( vertexMap[nIndex] < 0 ) { CopyVertex( pOutMesh->GetVertex(nOutVert), GetVertex(nIndex), m_nVertexStrideFloats ); vertexMap[nIndex] = nOutVert++; } pOutMesh->m_pIndices[nOutTri*3+j] = vertexMap[nIndex]; } nOutTri++; } }
void CConvexHullBuilder::FitOBBToFace( CHullTri *pTri, matrix3x4_t &xform, Vector &vExtents ) { BuildSilhouette( pTri, pTri->m_vNormal ); int nEdgeCount = m_silhouetteVertexList.Count(); int nMinEdge = 0; float flMinPerimeter = 1e24f; for ( int i = 0; i < nEdgeCount; i++ ) { int nNext = (i+1)%nEdgeCount; Vector v0 = *(Vector *)m_silhouetteVertexList[i]; Vector v1 = *(Vector *)m_silhouetteVertexList[nNext]; Vector vEdge = v1 - v0; Vector vNormalY = CrossProduct( pTri->m_vNormal, vEdge ); VectorNormalize( vNormalY ); float flExtentTestY = SupportExtents_Silhouette( vNormalY ); Vector vNormalZ = CrossProduct( pTri->m_vNormal, vNormalY ); float flExtentTestZ = SupportExtents_Silhouette( vNormalZ ); float flPerim = flExtentTestZ + flExtentTestY; if ( flPerim < flMinPerimeter ) { flMinPerimeter = flPerim; nMinEdge = i; } }
Vector vNormalX = pTri->m_vNormal; int nNext = (nMinEdge+1)%nEdgeCount; Vector vEdge = *(Vector *)m_silhouetteVertexList[nNext] - *(Vector *)m_silhouetteVertexList[nMinEdge]; Vector vNormalY = CrossProduct( pTri->m_vNormal, vEdge ); VectorNormalize( vNormalY ); Vector vNormalZ = CrossProduct( pTri->m_vNormal, vNormalY ); VectorNormalize( vNormalZ ); MatrixSetColumn( vNormalX, 0, xform ); MatrixSetColumn( vNormalY, 1, xform ); MatrixSetColumn( vNormalZ, 2, xform );
Vector vMins, vMaxs, vCenter; SupportExtents( pTri->m_vNormal, &vMins.x, &vMaxs.x ); SupportExtents_Silhouette( vNormalY, &vMins.y, &vMaxs.y ); SupportExtents_Silhouette( vNormalZ, &vMins.z, &vMaxs.z ); vCenter = 0.5f * (vMins + vMaxs); vExtents = vMaxs - vCenter; vCenter = vNormalX * vCenter.x + vNormalY * vCenter.y + vNormalZ * vCenter.z; MatrixSetColumn( vCenter, 3, xform );
}
void CConvexHullBuilder::FitOBBFast( matrix3x4_t &xform, Vector &vExtents ) { CHullTri *pTri = m_faceListNoVerts.Head();
if ( !pTri ) return;
CHullTri *pMinTri = pTri; float flMinX = 0, flMaxX = 0; float flExtentX = SupportExtents( pTri->m_vNormal, &flMinX, &flMaxX ); pTri = pTri->m_pNext;
for ( ; pTri != NULL; pTri = pTri->m_pNext ) { float flMinTest = 0, flMaxTest = 0; float flExtentTest = SupportExtents( pTri->m_vNormal, &flMinTest, &flMaxTest ); if ( flExtentTest < flExtentX ) { flExtentX = flExtentTest; flMinX = flMinTest; flMaxX = flMaxTest; pMinTri = pTri; } } FitOBBToFace( pMinTri, xform, vExtents ); }
void CConvexHullBuilder::FitOBB( matrix3x4_t &xform, Vector &vExtents ) { // try all faces, return minimum surface area box
CHullTri *pMinTri = NULL; Vector vTmpExtents; matrix3x4_t tmpXform; float flMinSurfacearea = 1e24; for ( CHullTri *pTri = m_faceListNoVerts.Head(); pTri != NULL; pTri = pTri->m_pNext ) { FitOBBToFace( pTri, tmpXform, vTmpExtents ); float flSurfaceArea = vTmpExtents.x * vTmpExtents.y + vTmpExtents.x * vTmpExtents.z + vTmpExtents.y * vTmpExtents.z; if ( flSurfaceArea < flMinSurfacearea ) { flMinSurfacearea = flSurfaceArea; pMinTri = pTri; } } FitOBBToFace( pMinTri, xform, vExtents ); }
// returns the mesh containing the convex hull of the input mesh
void ConvexHull3D( CMesh *pOutMesh, const CMesh &inputMesh, float flCoplanarEpsilon ) { CConvexHullBuilder builder( inputMesh.m_pVerts, inputMesh.m_nVertexCount, inputMesh.m_nVertexStrideFloats, flCoplanarEpsilon ); builder.BuildHull(); builder.GenerateOutputMesh( pOutMesh ); }
void FitOBBToMesh( matrix3x4_t *pCenter, Vector *pExtents, const CMesh &inputMesh, float flCoplanarEpsilon ) { CConvexHullBuilder builder( inputMesh.m_pVerts, inputMesh.m_nVertexCount, inputMesh.m_nVertexStrideFloats, flCoplanarEpsilon ); builder.BuildHull(); matrix3x4_t xform; Vector vExtents; builder.FitOBB( xform, vExtents ); if ( pCenter && pExtents ) { *pCenter = xform; *pExtents = vExtents; } }
Vector CSGInsidePoint( VPlane *pPlanes, int planeCount ) { Vector point = vec3_origin;
for ( int i = 0; i < planeCount; i++ ) { float d = DotProduct( pPlanes[i].m_Normal, point ) - pPlanes[i].m_Dist; if ( d < 0 ) { point -= d * pPlanes[i].m_Normal; } } return point; }
fltx4 CSGInsidePoint_SIMD( fltx4 *pPlanes, int nPlaneCount ) { fltx4 point = Four_Zeros;
// TODO: Depending of the number of planes that we have, it may be possible to actually calculate several planes at the same time.
// Have to see if the calculation is still correct when done in parallel (the offset may still be similar).
// Even with the current code complexity, it would be good to do 4 by 4 if we have at least 8 to 12 planes every time.
for ( int i = 0; i < nPlaneCount; i++ ) { fltx4 planeValue = pPlanes[i];
// float d = DotProduct( pPlanes[i].m_Normal, point ) - pPlanes[i].m_Dist;
fltx4 result = Dot3SIMD( planeValue, point ); // Fast on X360, not so fast on PC and PS3 - Still faster than FPU operation though
result = SubSIMD( result, SplatWSIMD( planeValue ) ); // XYZW has the result of the dot product
// if ( d < 0 )
// point -= d * pPlanes[i].m_Normal;
fltx4 offset = MulSIMD( result, planeValue ); // XYZ has d * normal, and W as garbage
bi32x4 mask = CmpLtSIMD( result, Four_Zeros ); // (d < 0) ? 0xffffffff : 0
point = SubSIMD( point, MaskedAssign( mask, offset, Four_Zeros ) ); // point will have garbage W, but this has no impact
// (as we use Dot3 product).
} point = SetWToZeroSIMD( point ); // Just in case
return point; }
void TranslatePlaneList( VPlane *pPlanes, int nPlaneCount, const Vector &offset ) { for ( int i = 0; i < nPlaneCount; i++ ) { pPlanes[i].m_Dist += DotProduct( offset, pPlanes[i].m_Normal ); } }
void TranslatePlaneList_SIMD( fltx4 *pPlanes, int nPlaneCount, const fltx4 &offset ) { int i = 0; fltx4 f4WMask = LoadAlignedSIMD( g_SIMD_ComponentMask[3] ); while ( nPlaneCount >= 4 ) { fltx4 plane0 = pPlanes[i]; fltx4 plane1 = pPlanes[i + 1]; fltx4 plane2 = pPlanes[i + 2]; fltx4 plane3 = pPlanes[i + 3];
fltx4 dot0 = Dot3SIMD( offset, plane0 ); fltx4 dot1 = Dot3SIMD( offset, plane1 ); fltx4 dot2 = Dot3SIMD( offset, plane2 ); fltx4 dot3 = Dot3SIMD( offset, plane3 );
dot0 = AndSIMD( dot0, f4WMask ); // 0 0 0 Dot
dot1 = AndSIMD( dot1, f4WMask ); // W contains Dist
dot2 = AndSIMD( dot2, f4WMask ); dot3 = AndSIMD( dot3, f4WMask );
pPlanes[i] = AddSIMD( plane0, dot0 ); pPlanes[i + 1] = AddSIMD( plane1, dot1 ); pPlanes[i + 2] = AddSIMD( plane2, dot2 ); pPlanes[i + 3] = AddSIMD( plane3, dot3 );
nPlaneCount -= 4; i += 4; } while ( nPlaneCount > 0 ) { fltx4 plane0 = pPlanes[i]; fltx4 dot0 = Dot3SIMD( offset, plane0 ); dot0 = AndSIMD( dot0, f4WMask ); // 0 0 0 Dot
pPlanes[i] = AddSIMD( plane0, dot0 ); ++i; --nPlaneCount; } }
void InvertPlanes( VPlane *pPlaneList, int nPlaneCount ) { for ( int i = 0; i < nPlaneCount; i++ ) { pPlaneList[i].m_Normal *= -1; pPlaneList[i].m_Dist *= -1; } }
void InvertPlanes_SIMD( fltx4 * pPlanes, int nPlaneCount ) { int i = 0; while ( nPlaneCount >= 4) { pPlanes[i] = -pPlanes[i]; pPlanes[i + 1] = -pPlanes[i + 1]; pPlanes[i + 2] = -pPlanes[i + 2]; pPlanes[i + 3] = -pPlanes[i + 3]; i += 4; nPlaneCount -= 4; }
while ( nPlaneCount > 0 ) { pPlanes[i] = -pPlanes[i]; ++i; --nPlaneCount; } }
void CSGPlaneList( CUtlVector<Vector> &verts, CUtlVector<uint32> &index, CUtlVector<uint32> &trianglePlaneIndices, VPlane *pPlaneList, int nPlaneCount, float flCoplanarEpsilon ) { const int MAX_VERTS = 128; Vector vertsIn[MAX_VERTS], vertsOut[MAX_VERTS];
// compute a point inside the volume defined by these planes
Vector insidePoint = CSGInsidePoint( pPlaneList, nPlaneCount ); // move the planes so that the inside point is at the origin
// NOTE: This is to maximize precision for the CSG operations
TranslatePlaneList( pPlaneList, nPlaneCount, -insidePoint );
// Build the CSG solid of this leaf given that the planes in the list define a convex solid
for ( int i = 0; i < nPlaneCount; ++i ) { // Build a big-ass poly in this plane
int vertCount = PolyFromPlane( vertsIn, pPlaneList[i].m_Normal, pPlaneList[i].m_Dist );
// Now chop it by every other plane
int j; for ( j = 0; j < nPlaneCount; ++j ) { // don't clip planes with themselves
if ( i == j ) continue;
// Less than a poly left, something is wrong, don't bother with this polygon
if ( vertCount < 3 ) continue;
// Chop the polygon against this plane
vertCount = ClipPolyToPlane( vertsIn, vertCount, vertsOut, pPlaneList[j].m_Normal, pPlaneList[j].m_Dist, flCoplanarEpsilon );
// Just copy the verts each time, don't bother swapping pointers (efficiency is not a goal here)
for ( int k = 0; k < vertCount; ++k ) { VectorCopy( vertsOut[k], vertsIn[k] ); } }
// We've got a polygon here
if ( vertCount >= 3 ) { // Since this is a convex polygon, use the fan algorithm to create the triangles
int baseVertex = verts.Count(); for ( int j = 0; j < vertCount - 2; ++j ) { trianglePlaneIndices.AddToTail(i); index.AddToTail( baseVertex ); index.AddToTail( baseVertex + j + 1 ); index.AddToTail( baseVertex + j + 2 ); }
// Copy verts
for ( int j = 0; j < vertCount; ++j ) { verts.AddToTail( vertsIn[j] + insidePoint ); } } } }
void CSGPlaneList_SIMD( CUtlVector<fltx4> &verts, CUtlVector<uint32> &index, CUtlVector<uint16> &trianglePlaneIndices, fltx4 *pPlaneList, int nPlaneCount, float flCoplanarEpsilon ) { const int MAX_VERTS = 128; fltx4 vertsIn[MAX_VERTS], vertsOut[MAX_VERTS]; #if _DEBUG
Vector tempVertsIn[MAX_VERTS], tempVertOuts[MAX_VERTS]; #endif
// compute a point inside the volume defined by these planes
fltx4 insidePoint = CSGInsidePoint_SIMD( pPlaneList, nPlaneCount );
#if _DEBUG
Vector vInsidePoint = CSGInsidePoint( (VPlane *)pPlaneList, nPlaneCount ); Assert( vInsidePoint.x == SubFloat( insidePoint, 0 ) ); Assert( vInsidePoint.y == SubFloat( insidePoint, 1 ) ); Assert( vInsidePoint.z == SubFloat( insidePoint, 2 ) ); #endif
// move the planes so that the inside point is at the origin
// NOTE: This is to maximize precision for the CSG operations
TranslatePlaneList_SIMD( pPlaneList, nPlaneCount, -insidePoint );
// Build the CSG solid of this leaf given that the planes in the list define a convex solid
for ( int i = 0; i < nPlaneCount; ++i ) { // Build a big-ass poly in this plane
PolyFromPlane_SIMD( vertsIn, pPlaneList[i] ); int nVertCount = 4; // PolyFromPlane actually always return 4. Bake the result.
#if _DEBUG
Vector normalI( SubFloat( pPlaneList[i], 0 ), SubFloat( pPlaneList[i], 1 ), SubFloat( pPlaneList[i], 2 ) ); int nResult = PolyFromPlane( tempVertsIn, normalI, SubFloat( pPlaneList[i], 3 ) ); Assert( nVertCount == nResult ); for (int n = 0 ; n < nVertCount ; ++n ) { Assert( tempVertsIn[n].x == SubFloat( vertsIn[n], 0 ) ); Assert( tempVertsIn[n].y == SubFloat( vertsIn[n], 1 ) ); Assert( tempVertsIn[n].z == SubFloat( vertsIn[n], 2 ) ); } #endif
// Now chop it by every other plane
int j; for ( j = 0; j < nPlaneCount; ++j ) { // don't clip planes with themselves
if ( i == j ) { continue; }
// Less than a poly left, something is wrong, don't bother with this polygon
if ( nVertCount < 3 ) { break; // Or with any other polygon (as nVertCount would not be set in the loop).
}
// Chop the polygon against this plane
#if _DEBUG
int nOldVertCount = nVertCount; #endif
nVertCount = ClipPolyToPlane_SIMD( vertsIn, nVertCount, vertsOut, pPlaneList[j], flCoplanarEpsilon );
#if _DEBUG
Vector normal( SubFloat( pPlaneList[j], 0 ), SubFloat( pPlaneList[j], 1 ), SubFloat( pPlaneList[j], 2 ) ); int nClipResult = ClipPolyToPlane( tempVertsIn, nOldVertCount, tempVertOuts, normal, SubFloat( pPlaneList[j], 3 ), flCoplanarEpsilon ); Assert( nClipResult == nVertCount ); for ( int n = 0 ; n < nVertCount ; ++n ) { // In some cases, the SIMD algorithm does not return the exact same result as the fpu version.
Assert( fabs( tempVertOuts[n].x - SubFloat( vertsOut[n], 0 ) ) < 0.001f ); Assert( fabs( tempVertOuts[n].y - SubFloat( vertsOut[n], 1 ) ) < 0.001f ); Assert( fabs( tempVertOuts[n].z - SubFloat( vertsOut[n], 2 ) ) < 0.001f ); } #endif
// Just copy the verts each time, don't bother swapping pointers (efficiency is not a goal here) - Not anymore :)
int nNumberOfVertices = nVertCount; int k = 0; while ( nNumberOfVertices >= 4) { vertsIn[k] = vertsOut[k]; vertsIn[k + 1] = vertsOut[k + 1]; vertsIn[k + 2] = vertsOut[k + 2]; vertsIn[k + 3] = vertsOut[k + 3]; #if _DEBUG
tempVertsIn[k] = tempVertOuts[k]; tempVertsIn[k + 1] = tempVertOuts[k + 1]; tempVertsIn[k + 2] = tempVertOuts[k + 2]; tempVertsIn[k + 3] = tempVertOuts[k + 3]; #endif
nNumberOfVertices -= 4; k += 4; }
while ( nNumberOfVertices > 0 ) { vertsIn[k] = vertsOut[k]; #if _DEBUG
tempVertsIn[k] = tempVertOuts[k]; #endif
--nNumberOfVertices; ++k; } }
// We've got a polygon here
if ( nVertCount >= 3 ) { // Since this is a convex polygon, use the fan algorithm to create the triangles
const int NUMBER_OF_TRIANGLES = nVertCount - 2; int nDestTrianglePlaneIndex = trianglePlaneIndices.Count(); int nDestIndex = index.Count(); // Set the count once (will grow as needed), so we don't call AddTail() several times.
trianglePlaneIndices.SetCountNonDestructively( nDestTrianglePlaneIndex + NUMBER_OF_TRIANGLES ); index.SetCountNonDestructively( nDestIndex + ( 3 * NUMBER_OF_TRIANGLES ) );
int nBaseVertex = verts.Count(); for ( int j = 0; j < NUMBER_OF_TRIANGLES; ++j ) { trianglePlaneIndices[nDestTrianglePlaneIndex] = i; index[nDestIndex] = nBaseVertex; index[nDestIndex + 1] = nBaseVertex + j + 1; index[nDestIndex + 2] = nBaseVertex + j + 2; ++nDestTrianglePlaneIndex; nDestIndex += 3; }
// Copy verts
int nNumberOfVertices = nVertCount; int nDestVertIndex = verts.Count(); // Set the count once (will grow as needed), so we don't call AddTail() several times.
verts.SetCountNonDestructively( nDestVertIndex + nNumberOfVertices ); int k = 0; while ( nNumberOfVertices >= 4 ) { fltx4 result0 = AddSIMD( vertsIn[k], insidePoint ); fltx4 result1 = AddSIMD( vertsIn[k + 1], insidePoint ); fltx4 result2 = AddSIMD( vertsIn[k + 2], insidePoint ); fltx4 result3 = AddSIMD( vertsIn[k + 3], insidePoint ); verts[nDestVertIndex] = result0; verts[nDestVertIndex + 1] = result1; verts[nDestVertIndex + 2] = result2; verts[nDestVertIndex + 3] = result3; nNumberOfVertices -= 4; k += 4; nDestVertIndex += 4; } while ( nNumberOfVertices > 0 ) { fltx4 result = AddSIMD( vertsIn[k], insidePoint ); verts[nDestVertIndex] = result; --nNumberOfVertices; ++k; ++nDestVertIndex; } } } }
void HullFromPlanes( CMesh *pOutMesh, CUtlVector<uint32> *pTrianglePlaneIndices, const float *pPlanesInput, int nPlaneCount, int nPlaneStrideFloats, float flCoplanarEpsilon ) { CUtlVector<VPlane> planes; planes.SetCount( nPlaneCount ); const float *pPlane = pPlanesInput; for ( int i = 0; i < nPlaneCount; i++, pPlane += nPlaneStrideFloats ) { planes[i].m_Normal.Init( pPlane[0], pPlane[1], pPlane[2] ); planes[i].m_Dist = pPlane[3]; } // the clipping code returns polys IN FRONT of the plane, whereas our interface has planes pointing out of the solid
InvertPlanes( planes.Base(), planes.Count() ); CUtlVector<Vector> verts; CUtlVector<uint32> index; CUtlVector<uint32> trianglePlaneIndices; CSGPlaneList( verts, index, trianglePlaneIndices, planes.Base(), planes.Count(), flCoplanarEpsilon ); if ( verts.Count() ) { pOutMesh->AllocateAndCopyMesh( verts.Count(), (float *)verts.Base(), index.Count(), index.Base(), 3, 0, 0 ); } // If the caller wants the index of the originating plane of each triangle, put it in the output.
if( pTrianglePlaneIndices ) pTrianglePlaneIndices->Swap( trianglePlaneIndices ); }
// Optimized version where there is no need to copy, planes are modified in place (assuming that the list has been created dynamically), and we use VMX intensively
void HullFromPlanes_SIMD( CMesh *pOutMesh, CUtlVector<uint16> *pTrianglePlaneIndices, fltx4 *pPlanesInput, int nPlaneCount, float flCoplanarEpsilon ) { #if _DEBUG
CUtlVector<VPlane> planes; planes.SetCount( nPlaneCount ); const float *pPlane = (const float *)pPlanesInput; for ( int i = 0; i < nPlaneCount; i++, pPlane += 4 ) { planes[i].m_Normal.Init( pPlane[0], pPlane[1], pPlane[2] ); planes[i].m_Dist = pPlane[3]; } // the clipping code returns polys IN FRONT of the plane, whereas our interface has planes pointing out of the solid
InvertPlanes( planes.Base(), planes.Count() ); #endif
// the clipping code returns polys IN FRONT of the plane, whereas our interface has planes pointing out of the solid
InvertPlanes_SIMD( pPlanesInput, nPlaneCount ); // Too bad we are doing this here instead of in the caller code (when creating the array).
// At the same time, this is implementation specific that the caller should not be aware of
CUtlVector<fltx4> verts; CUtlVector<uint32> index; CUtlVector<uint16> trianglePlaneIndices; CSGPlaneList_SIMD( verts, index, trianglePlaneIndices, pPlanesInput, nPlaneCount, flCoplanarEpsilon );
#if _DEBUG
CUtlVector<Vector> vertsSlow; CUtlVector<uint32> indexSlow; CUtlVector<uint32> trianglePlaneIndicesSlow; CSGPlaneList( vertsSlow, indexSlow, trianglePlaneIndicesSlow, planes.Base(), planes.Count(), flCoplanarEpsilon );
Assert( verts.Count() == vertsSlow.Count() ); Assert( index.Count() == indexSlow.Count() ); Assert( trianglePlaneIndices.Count() == trianglePlaneIndicesSlow.Count() );
for (int i = 0 ; i < verts.Count() ; ++i ) { Assert( SubFloat( verts[i], 0 ) == vertsSlow[i].x ); Assert( SubFloat( verts[i], 1 ) == vertsSlow[i].y ); Assert( SubFloat( verts[i], 2 ) == vertsSlow[i].z ); } for (int i = 0 ; i < index.Count() ; ++i ) { Assert( index[i] == indexSlow[i] ); } for (int i = 0 ; i < trianglePlaneIndices.Count() ; ++i ) { Assert( trianglePlaneIndices[i] == trianglePlaneIndicesSlow[i] ); } #endif
// Do we really need a mesh in our use case?
if ( verts.Count() ) { pOutMesh->AllocateAndCopyMesh( verts.Count(), (float *)verts.Base(), index.Count(), index.Base(), 4, 0, 0 ); }
// If the caller wants the index of the originating plane of each triangle, put it in the output.
if( pTrianglePlaneIndices ) { pTrianglePlaneIndices->Swap( trianglePlaneIndices ); } }
|