You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
453 lines
13 KiB
453 lines
13 KiB
//========= Copyright Valve Corporation, All rights reserved. ============// |
|
#include "stdafx.h" |
|
#include "simplify.h" |
|
extern IPhysicsCollision *physcollision; |
|
|
|
extern bool g_bQuiet; |
|
|
|
const float DIST_EPSILON = 1.0f / 32.0f; |
|
// this is the list of candidate planes that will be added one by one to the convex hull |
|
// until none of the surface lies outside the tolerance |
|
struct planetest_t |
|
{ |
|
Vector normal; |
|
float dist; |
|
int inUse; |
|
float bestDist; |
|
void Init( int axis, float sign, float _dist, bool _inUse = false ) |
|
{ |
|
memset( this, 0, sizeof(*this) ); |
|
normal[axis] = sign; |
|
dist = sign*_dist; |
|
inUse = _inUse; |
|
bestDist = -1; |
|
} |
|
void Init( const Vector &a, const Vector &b, const Vector &c, bool _inUse = false ) |
|
{ |
|
Vector e0 = b-a; |
|
Vector e1 = c-a; |
|
normal = CrossProduct( e1, e0 ); |
|
VectorNormalize( normal ); |
|
dist = DotProduct( normal, a ); |
|
inUse = _inUse; |
|
bestDist = -1; |
|
} |
|
}; |
|
|
|
CPhysConvex *ConvertPlaneListToConvex( CUtlVector<planetest_t> &list ) |
|
{ |
|
float temp[4 * 2048]; |
|
struct listplane_t |
|
{ |
|
float plane[4]; |
|
}; |
|
|
|
int planeCount = 0; |
|
listplane_t *pList = (listplane_t *)temp; |
|
for ( int i = 0; i < list.Count(); i++ ) |
|
{ |
|
if ( list[i].inUse ) |
|
{ |
|
list[i].normal.CopyToArray( pList[planeCount].plane ); |
|
pList[planeCount].plane[3] = list[i].dist; |
|
planeCount++; |
|
} |
|
} |
|
|
|
return physcollision->ConvexFromPlanes( temp, planeCount, 0.25f ); |
|
} |
|
|
|
Vector BoxSupport( const Vector &dir, const Vector &mins, const Vector &maxs ) |
|
{ |
|
Vector out; |
|
for ( int i = 0; i < 3; i++ ) |
|
{ |
|
out[i] = (dir[i] >= 0) ? maxs[i] : mins[i]; |
|
} |
|
return out; |
|
} |
|
|
|
struct convexoptimize_t |
|
{ |
|
CUtlVector<planetest_t> list; |
|
float targetTolerance; |
|
|
|
void InitPlanes( CPhysCollide *pCollide, bool addAABBToSimplifiedHull ) |
|
{ |
|
Vector mins, maxs; |
|
physcollision->CollideGetAABB( &mins, &maxs, pCollide, vec3_origin, vec3_angle ); |
|
if ( !addAABBToSimplifiedHull ) |
|
{ |
|
mins -= Vector(targetTolerance,targetTolerance,targetTolerance); |
|
maxs += Vector(targetTolerance,targetTolerance,targetTolerance); |
|
} |
|
int i; |
|
for ( i = 0; i < 3; i++ ) |
|
{ |
|
planetest_t &elem = list[list.AddToTail()]; |
|
elem.Init( i, 1.0f, maxs[i], true ); |
|
planetest_t &elem2 = list[list.AddToTail()]; |
|
elem2.Init( i, -1.0f, mins[i], true ); |
|
} |
|
ICollisionQuery *pQuery = physcollision->CreateQueryModel( pCollide ); |
|
Vector triVerts[3]; |
|
for ( i = 0; i < pQuery->TriangleCount(0); i++ ) |
|
{ |
|
pQuery->GetTriangleVerts( 0, i, triVerts ); |
|
planetest_t &elem = list[list.AddToTail()]; |
|
elem.Init( triVerts[0], triVerts[1], triVerts[2], false ); |
|
elem.bestDist = DotProduct( elem.normal, BoxSupport(elem.normal, mins, maxs) ) - elem.dist; |
|
} |
|
physcollision->DestroyQueryModel( pQuery ); |
|
} |
|
|
|
CPhysConvex *ConvertToConvex() |
|
{ |
|
return ::ConvertPlaneListToConvex( list ); |
|
} |
|
|
|
int FindBestPlane( float dist ) |
|
{ |
|
int best = -1; |
|
for ( int i = 6; i < list.Count(); i++ ) |
|
{ |
|
if ( list[i].inUse ) |
|
continue; |
|
if ( dist >= list[i].bestDist ) |
|
continue; |
|
dist = list[i].bestDist; |
|
best = i; |
|
} |
|
return best; |
|
} |
|
|
|
bool AddBestPlane() |
|
{ |
|
convertconvexparams_t params; |
|
params.Defaults(); |
|
CPhysConvex *pConvex = ConvertPlaneListToConvex( list ); |
|
CPhysCollide *pCurrentCollide = physcollision->ConvertConvexToCollideParams( &pConvex, 1, params ); |
|
int bestIndex = -1; |
|
float bestDist = 0; |
|
while ( true ) |
|
{ |
|
if ( bestIndex >= 0 ) |
|
{ |
|
list[bestIndex].inUse = true; |
|
} |
|
int test = FindBestPlane( bestDist ); |
|
if ( test < 0 ) |
|
break; |
|
if ( bestIndex >= 0 ) |
|
{ |
|
list[bestIndex].inUse = false; |
|
} |
|
Vector dir = list[test].normal; |
|
Vector point = physcollision->CollideGetExtent( pCurrentCollide, vec3_origin, vec3_angle, dir ); |
|
float before = DotProduct( dir, point ); |
|
list[test].inUse = true; |
|
pConvex = ConvertToConvex(); |
|
list[test].inUse = false; |
|
CPhysCollide *pCollide = physcollision->ConvertConvexToCollideParams( &pConvex, 1, params ); |
|
Vector p2 = physcollision->CollideGetExtent( pCollide, vec3_origin, vec3_angle, dir ); |
|
physcollision->DestroyCollide( pCollide ); |
|
float after = DotProduct( dir, p2 ); |
|
list[test].bestDist = fabs(before-after); |
|
if ( list[test].bestDist > bestDist ) |
|
{ |
|
bestDist = list[test].bestDist; |
|
bestIndex = test; |
|
} |
|
} |
|
physcollision->DestroyCollide( pCurrentCollide ); |
|
|
|
if ( bestIndex >= 0 && bestDist >= targetTolerance ) |
|
{ |
|
list[bestIndex].inUse = true; |
|
return true; |
|
} |
|
|
|
return false; |
|
} |
|
}; |
|
|
|
CPhysConvex *SimplifyConvexFromVerts( Vector **verts, int vertCount, bool addAABBToSimplifiedHull, float tolerance, int index ) |
|
{ |
|
CPhysConvex *pConvex = physcollision->ConvexFromVerts( verts, vertCount ); |
|
float targetVolume = physcollision->ConvexVolume( pConvex ); |
|
// can't simplify this polyhedron |
|
if ( vertCount <= 8 ) |
|
return pConvex; |
|
|
|
convexoptimize_t opt; |
|
memset( &opt, 0, sizeof(opt)); |
|
opt.targetTolerance = tolerance; |
|
convertconvexparams_t params; |
|
params.Defaults(); |
|
CPhysCollide *pRef = physcollision->ConvertConvexToCollideParams( &pConvex, 1, params ); |
|
opt.InitPlanes( pRef, addAABBToSimplifiedHull ); |
|
physcollision->DestroyCollide( pRef ); |
|
|
|
// Simplify until you hit the tolerance |
|
int i; |
|
for ( i = 0; i < vertCount; i++ ) |
|
{ |
|
if ( !opt.AddBestPlane() ) |
|
break; |
|
} |
|
|
|
// Create the output shape |
|
pConvex = opt.ConvertToConvex(); |
|
float currentVolume = physcollision->ConvexVolume( pConvex ); |
|
//Msg("%d iterations, for convex %d\n", i, index ); |
|
|
|
return pConvex; |
|
} |
|
|
|
inline int AddVert( Vector **ppVerts, int vertCount, const Vector &newVert ) |
|
{ |
|
for ( int i = 0; i < vertCount; i++ ) |
|
{ |
|
if ( fabs(ppVerts[i]->x - newVert.x) < DIST_EPSILON && |
|
fabs(ppVerts[i]->y - newVert.y) < DIST_EPSILON && |
|
fabs(ppVerts[i]->z - newVert.z) < DIST_EPSILON ) |
|
return vertCount; |
|
} |
|
*ppVerts[vertCount] = newVert; |
|
return vertCount+1; |
|
} |
|
|
|
void BuildSingleConvex( CPhysConvex **convexListOut, ICollisionQuery *pQuery, Vector **ppVerts, const simplifyparams_t ¶ms ) |
|
{ |
|
int vertCount = 0; |
|
for ( int i = 0; i < pQuery->ConvexCount(); i++ ) |
|
{ |
|
Vector v[3]; |
|
for ( int j = 0; j < pQuery->TriangleCount(i); j++ ) |
|
{ |
|
pQuery->GetTriangleVerts( i, j, v ); |
|
vertCount = AddVert( ppVerts, vertCount, v[0] ); |
|
vertCount = AddVert( ppVerts, vertCount, v[1] ); |
|
vertCount = AddVert( ppVerts, vertCount, v[2] ); |
|
} |
|
} |
|
convexListOut[0] = SimplifyConvexFromVerts( ppVerts, vertCount, params.addAABBToSimplifiedHull, params.tolerance, 0 ); |
|
physcollision->SetConvexGameData( convexListOut[0], pQuery->GetGameData( 0 ) ); |
|
} |
|
|
|
void SimplifyConvexElements( CPhysConvex **convexListOut, ICollisionQuery *pQuery, Vector **ppVerts, const simplifyparams_t ¶ms ) |
|
{ |
|
for ( int i = 0; i < pQuery->ConvexCount(); i++ ) |
|
{ |
|
int vertCount = 0; |
|
Vector v[3]; |
|
for ( int j = 0; j < pQuery->TriangleCount(i); j++ ) |
|
{ |
|
pQuery->GetTriangleVerts( i, j, v ); |
|
vertCount = AddVert( ppVerts, vertCount, v[0] ); |
|
vertCount = AddVert( ppVerts, vertCount, v[1] ); |
|
vertCount = AddVert( ppVerts, vertCount, v[2] ); |
|
} |
|
convexListOut[i] = SimplifyConvexFromVerts( ppVerts, vertCount, params.addAABBToSimplifiedHull, params.tolerance, i ); |
|
physcollision->SetConvexGameData( convexListOut[i], pQuery->GetGameData( i ) ); |
|
} |
|
} |
|
|
|
struct mergeconvex_t |
|
{ |
|
byte mergeCount; |
|
byte list[255]; |
|
}; |
|
|
|
void MergeElems( CUtlVector<mergeconvex_t> &elems, int index0, int index1 ) |
|
{ |
|
Assert( index0 < index1 ); |
|
for (int i = 0; i < elems[index1].mergeCount; i++) |
|
{ |
|
elems[index0].list[i+elems[index0].mergeCount] = elems[index1].list[i]; |
|
} |
|
elems[index0].mergeCount += elems[index1].mergeCount; |
|
elems.FastRemove(index1); |
|
} |
|
|
|
int VertsForElem( ICollisionQuery *pQuery, Vector **ppVerts, const mergeconvex_t &elems0, int vertCount ) |
|
{ |
|
for ( int i = 0; i < elems0.mergeCount; i++ ) |
|
{ |
|
int convexId = elems0.list[i]; |
|
Vector v[3]; |
|
for ( int j = 0; j < pQuery->TriangleCount(convexId); j++ ) |
|
{ |
|
pQuery->GetTriangleVerts( convexId, j, v ); |
|
vertCount = AddVert( ppVerts, vertCount, v[0] ); |
|
vertCount = AddVert( ppVerts, vertCount, v[1] ); |
|
vertCount = AddVert( ppVerts, vertCount, v[2] ); |
|
} |
|
} |
|
return vertCount; |
|
} |
|
|
|
void PlanesForElem( ICollisionQuery *pQuery, CUtlVector<float> &planes, const mergeconvex_t &elem0 ) |
|
{ |
|
for ( int i = 0; i < elem0.mergeCount; i++ ) |
|
{ |
|
int convexId = elem0.list[i]; |
|
Vector v[3]; |
|
for ( int j = 0; j < pQuery->TriangleCount(convexId); j++ ) |
|
{ |
|
pQuery->GetTriangleVerts( convexId, j, v ); |
|
Vector e0 = v[1]-v[0]; |
|
Vector e1 = v[2]-v[0]; |
|
Vector normal = CrossProduct( e1, e0 ); |
|
VectorNormalize( normal ); |
|
float dist = DotProduct( normal, v[0] ); |
|
planes.AddToTail( normal.x ); |
|
planes.AddToTail( normal.y ); |
|
planes.AddToTail( normal.z ); |
|
planes.AddToTail( dist ); |
|
} |
|
} |
|
} |
|
|
|
float ConvexVolumeFromPlanes( CUtlVector<float> &planes ) |
|
{ |
|
CPhysConvex *pConvex = planes.Count() ? physcollision->ConvexFromPlanes( planes.Base(), planes.Count()/4, DIST_EPSILON ) : NULL; |
|
float volume = 0; |
|
if ( pConvex ) |
|
{ |
|
volume = physcollision->ConvexVolume(pConvex); |
|
physcollision->ConvexFree(pConvex); |
|
} |
|
return volume; |
|
} |
|
|
|
float MergedDeltaVolume( ICollisionQuery *pQuery, Vector **ppVerts, const mergeconvex_t &elem0, const mergeconvex_t &elem1 ) |
|
{ |
|
// build vert list |
|
int vertCount = VertsForElem( pQuery, ppVerts, elem0, 0 ); |
|
// merge in next element |
|
vertCount = VertsForElem( pQuery, ppVerts, elem1, vertCount); |
|
CPhysConvex *pConvex = physcollision->ConvexFromVerts( ppVerts, vertCount ); |
|
float finalVolume = physcollision->ConvexVolume(pConvex); |
|
physcollision->ConvexFree(pConvex); |
|
|
|
CUtlVector<float> planes; |
|
PlanesForElem( pQuery, planes, elem0 ); |
|
float vol0 = ConvexVolumeFromPlanes( planes ); |
|
planes.RemoveAll(); |
|
PlanesForElem( pQuery, planes, elem1 ); |
|
float vol1 = ConvexVolumeFromPlanes( planes ); |
|
PlanesForElem( pQuery, planes, elem0 ); |
|
|
|
float volInt = ConvexVolumeFromPlanes( planes ); |
|
|
|
return finalVolume - (vol0+vol1-volInt); |
|
} |
|
|
|
int MergeAndSimplifyConvexElements( CPhysConvex **convexListOut, const CPhysCollide *pCollideIn, ICollisionQuery *pQuery, Vector **ppVerts, const simplifyparams_t ¶ms ) |
|
{ |
|
Assert( pQuery->ConvexCount() < 256 ); |
|
if ( pQuery->ConvexCount() > 256 ) |
|
{ |
|
SimplifyConvexElements(convexListOut, pQuery, ppVerts, params); |
|
return pQuery->ConvexCount(); |
|
} |
|
|
|
CUtlVector<mergeconvex_t> elems; |
|
int i; |
|
elems.EnsureCount(pQuery->ConvexCount()); |
|
float totalVolume = physcollision->CollideVolume( (CPhysCollide *)pCollideIn ); |
|
for ( i = 0; i < pQuery->ConvexCount(); i++ ) |
|
{ |
|
elems[i].mergeCount = 1; |
|
elems[i].list[0] = i; |
|
} |
|
loop: |
|
for ( i = 0; i < elems.Count(); i++ ) |
|
{ |
|
for ( int j = i+1; j < elems.Count(); j++ ) |
|
{ |
|
float volume = fabs(MergedDeltaVolume( pQuery, ppVerts, elems[i], elems[j] )); |
|
volume /= totalVolume; |
|
if ( volume < params.mergeConvexTolerance ) |
|
{ |
|
MergeElems( elems, i, j ); |
|
goto loop; |
|
} |
|
} |
|
} |
|
|
|
for ( i = 0; i < elems.Count(); i++ ) |
|
{ |
|
int vertCount = VertsForElem( pQuery, ppVerts, elems[i], 0 ); |
|
convexListOut[i] = SimplifyConvexFromVerts( ppVerts, vertCount, params.addAABBToSimplifiedHull, params.tolerance, i ); |
|
physcollision->SetConvexGameData( convexListOut[i], pQuery->GetGameData( elems[i].list[0] ) ); |
|
} |
|
return elems.Count(); |
|
} |
|
|
|
CPhysCollide *SimplifyCollide( CPhysCollide *pCollideIn, int indexIn, const simplifyparams_t ¶ms ) |
|
{ |
|
int sizeIn = physcollision->CollideSize( pCollideIn ); |
|
ICollisionQuery *pQuery = physcollision->CreateQueryModel( pCollideIn ); |
|
int maxVertCount = 0; |
|
int i; |
|
for ( i = pQuery->ConvexCount(); --i >= 0; ) |
|
{ |
|
int vertCount = pQuery->TriangleCount(i)*3; |
|
maxVertCount += vertCount; |
|
} |
|
|
|
Vector **ppVerts = new Vector *[maxVertCount]; |
|
Vector *verts = new Vector[maxVertCount]; |
|
for ( i = 0; i < maxVertCount; i++ ) |
|
{ |
|
ppVerts[i] = &verts[i]; |
|
} |
|
|
|
int outputConvexCount = params.forceSingleConvex ? 1 : pQuery->ConvexCount(); |
|
CPhysConvex **convexList = new CPhysConvex *[outputConvexCount]; |
|
if ( params.forceSingleConvex ) |
|
{ |
|
BuildSingleConvex( convexList, pQuery, ppVerts, params ); |
|
} |
|
else if ( params.mergeConvexElements && pQuery->ConvexCount() > 1 ) |
|
{ |
|
outputConvexCount = MergeAndSimplifyConvexElements( convexList, pCollideIn, pQuery, ppVerts, params ); |
|
if ( !g_bQuiet && pQuery->ConvexCount() != outputConvexCount) |
|
{ |
|
Msg("Simplified %d to %d elements\n", pQuery->ConvexCount(), outputConvexCount ); |
|
} |
|
} |
|
else |
|
{ |
|
SimplifyConvexElements( convexList, pQuery, ppVerts, params ); |
|
} |
|
convertconvexparams_t params; |
|
params.Defaults(); |
|
params.buildOuterConvexHull = true; |
|
params.buildDragAxisAreas = false; |
|
|
|
CPhysCollide *pCollideOut = physcollision->ConvertConvexToCollideParams( convexList, outputConvexCount, params ); |
|
|
|
// copy the drag axis areas from the source |
|
Vector dragAxisAreas = physcollision->CollideGetOrthographicAreas( pCollideIn ); |
|
physcollision->CollideSetOrthographicAreas( pCollideOut, dragAxisAreas ); |
|
|
|
physcollision->DestroyQueryModel( pQuery ); |
|
delete[] convexList; |
|
delete[] verts; |
|
delete[] ppVerts; |
|
|
|
if ( physcollision->CollideSize(pCollideOut) >= sizeIn ) |
|
{ |
|
// make a copy of the input collide |
|
physcollision->DestroyCollide(pCollideOut); |
|
char *pBuf = new char[sizeIn]; |
|
physcollision->CollideWrite( pBuf, pCollideIn ); |
|
pCollideOut = physcollision->UnserializeCollide( pBuf, sizeIn, indexIn ); |
|
delete[] pBuf; |
|
} |
|
return pCollideOut; |
|
} |
|
|
|
|