Paranoia2/utils/p2bsp/partition.cpp

667 lines
15 KiB
C++

/***
*
* Copyright (c) 1996-2002, Valve LLC. All rights reserved.
*
* This product contains software technology licensed from Id
* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc.
* All Rights Reserved.
*
****/
#include "bsp5.h"
#define MarkFacesCount( mf ) ( (mf) ? mf->count : 0 )
typedef struct
{
face_t **array;
int count;
int reserve;
} markfaces_t;
typedef struct surfnode_s
{
int size; // can be zero, which invalidates mins and maxs
int size_discardable;
vec3_t mins;
vec3_t maxs;
bool isleaf;
// node
surfnode_s *children[2];
markfaces_t *nodefaces;
int nodefaces_discardablesize;
// leaf
markfaces_t *leaffaces;
} surfnode_t;
typedef struct
{
bool dontbuild;
vec_t epsilon; // if a face is not epsilon far from the splitting plane, put it in result.middle
surfnode_t *headnode;
// result
int frontsize;
int backsize;
markfaces_t *middle; // may contains coplanar faces and discardable(SOLIDHINT) faces
} surftree_t;
static double g_splitvalue[MAX_INTERNAL_MAP_PLANES][2];
// organize all surfaces into a tree structure to accelerate intersection test
// can reduce more than 90% compile time for very complicated maps
surfnode_t *AllocSurfNode( void )
{
return (surfnode_t *)Mem_Alloc( sizeof( surfnode_t ), C_LEAFNODE );
}
void FreeSurfNode( surfnode_t *node )
{
Mem_Free( node, C_LEAFNODE );
}
markfaces_t *AllocMarkFaces( void )
{
return (markfaces_t *)Mem_Alloc( sizeof( markfaces_t ));
}
void FreeMarkFaces( markfaces_t **mf )
{
if( !mf || !*mf ) return;
if((*mf)->array )
Mem_Free((*mf)->array );
Mem_Free( *mf );
*mf = NULL;
}
void InsertMarkFace( markfaces_t *mf, face_t *f )
{
if( !mf ) return;
if( mf->reserve <= 0 )
{
mf->reserve = 256; // alloc reserve to avoid realloc on each face
mf->array = (face_t **)Mem_Realloc( mf->array, sizeof( face_t* ) * ( mf->count + mf->reserve + 1 ));
}
mf->array[mf->count++] = f;
mf->array[mf->count] = NULL;
mf->reserve--;
}
void ClearMarkFaces( markfaces_t *mf )
{
if( !mf ) return;
if( mf->array )
{
Mem_Free( mf->array );
mf->array = NULL;
}
mf->reserve = mf->count = 0;
}
void BuildSurfaceTree_r( surftree_t *tree, surfnode_t *node )
{
face_t *f, **fp;
node->size = MarkFacesCount( node->leaffaces );
node->size_discardable = 0;
if( node->size == 0 )
{
node->isleaf = true;
return;
}
ClearBounds( node->mins, node->maxs );
for( fp = node->leaffaces->array; fp && *fp != NULL; fp++ )
{
f = *fp;
WindingBounds( f->w, node->mins, node->maxs, true );
if( f->facestyle == face_discardable )
node->size_discardable++;
}
int bestaxis = -1;
vec_t bestdelta = 0;
for( int k = 0; k < 3; k++ )
{
if( node->maxs[k] - node->mins[k] > bestdelta + BSPCHOP_EPSILON )
{
bestdelta = node->maxs[k] - node->mins[k];
bestaxis = k;
}
}
if( node->size <= 5 || tree->dontbuild || bestaxis == -1 )
{
node->isleaf = true;
return;
}
vec_t dist, dist1, dist2;
node->isleaf = false;
dist = (node->mins[bestaxis] + node->maxs[bestaxis]) * 0.5;
dist1 = (3 * node->mins[bestaxis] + node->maxs[bestaxis]) * 0.25;
dist2 = (node->mins[bestaxis] + 3 * node->maxs[bestaxis]) * 0.25;
// each child node is at most 3/4 the size of the parent node.
// Most faces should be passed to a child node, faces left in the
// parent node are the ones whose dimensions are large enough to
// be comparable to the dimension of the parent node.
node->nodefaces = AllocMarkFaces();
node->nodefaces_discardablesize = 0;
node->children[0] = AllocSurfNode();
node->children[0]->leaffaces = AllocMarkFaces();
node->children[1] = AllocSurfNode();
node->children[1]->leaffaces = AllocMarkFaces();
for( fp = node->leaffaces->array; fp && *fp != NULL; fp++ )
{
f = *fp;
winding_t *w = f->w;
vec_t low = BOGUS_RANGE;
vec_t high = -BOGUS_RANGE;
if( !w || w->numpoints < 3 )
continue;
for( int x = 0; x < w->numpoints; x++ )
{
low = Q_min( low, w->p[x][bestaxis] );
high = Q_max( high, w->p[x][bestaxis] );
}
if( low < dist1 + BSPCHOP_EPSILON && high > dist2 - BSPCHOP_EPSILON )
{
InsertMarkFace( node->nodefaces, f );
if( f->facestyle == face_discardable )
node->nodefaces_discardablesize++;
}
else if( low >= dist1 && high <= dist2 )
{
if(( low + high ) * 0.5 > dist )
{
InsertMarkFace( node->children[0]->leaffaces, f );
}
else
{
InsertMarkFace( node->children[1]->leaffaces, f );
}
}
else if( low >= dist1 )
{
InsertMarkFace( node->children[0]->leaffaces, f );
}
else if( high <= dist2 )
{
InsertMarkFace( node->children[1]->leaffaces, f );
}
}
int leafcount = MarkFacesCount( node->leaffaces );
if( MarkFacesCount( node->children[0]->leaffaces ) == leafcount || MarkFacesCount( node->children[1]->leaffaces ) == leafcount )
{
MsgDev( D_WARN, "BuildSurfaceTree_r: didn't split the node\n" );
FreeMarkFaces( &node->children[0]->leaffaces );
FreeMarkFaces( &node->children[1]->leaffaces );
FreeSurfNode( node->children[0] );
FreeSurfNode( node->children[1] );
FreeMarkFaces( &node->nodefaces );
node->isleaf = true;
return;
}
FreeMarkFaces( &node->leaffaces );
BuildSurfaceTree_r( tree, node->children[0] );
BuildSurfaceTree_r( tree, node->children[1] );
}
surftree_t *BuildSurfaceTree( surface_t *surfaces, vec_t epsilon )
{
surftree_t *tree = (surftree_t *)Mem_Alloc( sizeof( surftree_t ), C_BSPTREE );
surface_t *p2;
face_t *f;
tree->headnode = AllocSurfNode();
tree->headnode->leaffaces = AllocMarkFaces();
tree->middle = AllocMarkFaces();
tree->epsilon = epsilon;
for( p2 = surfaces; p2 != NULL; p2 = p2->next )
{
if( p2->onnode )
continue;
for( f = p2->faces; f != NULL; f = f->next )
{
InsertMarkFace( tree->headnode->leaffaces, f );
InsertMarkFace( tree->middle, f );
}
}
tree->dontbuild = MarkFacesCount( tree->headnode->leaffaces ) < 20;
BuildSurfaceTree_r( tree, tree->headnode );
if( tree->dontbuild )
{
tree->backsize = 0;
tree->frontsize = 0;
}
else ClearMarkFaces( tree->middle );
return tree;
}
void TestSurfaceTree_r( surftree_t *tree, const surfnode_t *node, const plane_t *split )
{
vec_t low, high;
face_t **fp;
if( node->size == 0 )
return;
low = high = -split->dist;
for( int k = 0; k < 3; k++ )
{
if( split->normal[k] >= 0 )
{
high += split->normal[k] * node->maxs[k];
low += split->normal[k] * node->mins[k];
}
else
{
high += split->normal[k] * node->mins[k];
low += split->normal[k] * node->maxs[k];
}
}
if( low > tree->epsilon )
{
tree->frontsize += node->size;
tree->frontsize -= node->size_discardable;
return;
}
if( high < -tree->epsilon )
{
tree->backsize += node->size;
tree->backsize -= node->size_discardable;
return;
}
if( node->isleaf )
{
for( fp = node->leaffaces->array; fp && *fp != NULL; fp++ )
{
InsertMarkFace( tree->middle, *fp );
}
}
else
{
for( fp = node->nodefaces->array; fp && *fp != NULL; fp++ )
{
InsertMarkFace( tree->middle, *fp );
}
TestSurfaceTree_r( tree, node->children[0], split );
TestSurfaceTree_r( tree, node->children[1], split );
}
}
void TestSurfaceTree( surftree_t *tree, const plane_t *split )
{
if( tree->dontbuild )
return;
ClearMarkFaces( tree->middle );
tree->backsize = tree->frontsize = 0;
TestSurfaceTree_r( tree, tree->headnode, split );
}
void DeleteSurfaceTree_r( surfnode_t *node )
{
if( node->isleaf )
{
FreeMarkFaces( &node->leaffaces );
}
else
{
DeleteSurfaceTree_r( node->children[0] );
FreeSurfNode( node->children[0] );
DeleteSurfaceTree_r( node->children[1] );
FreeSurfNode( node->children[1] );
FreeMarkFaces( &node->nodefaces );
}
}
void DeleteSurfaceTree( surftree_t *tree )
{
DeleteSurfaceTree_r( tree->headnode );
FreeSurfNode( tree->headnode );
FreeMarkFaces( &tree->middle );
Mem_Free( tree, C_BSPTREE );
}
/*
==================
FaceSide
For BSP hueristic
==================
*/
static int FaceSide( const face_t *in, const plane_t *split, double *epsilonsplit = NULL )
{
vec_t d_front = 0.0;
vec_t d_back = 0.0;
vec_t dot;
winding_t *w;
vec_t *p;
int i;
ASSERT( in && in->w );
w = in->w;
// axial planes are fast
if( split->type <= PLANE_LAST_AXIAL )
{
for( i = 0, p = w->p[0] + split->type; i < w->numpoints; i++, p += 3 )
{
dot = *p - split->dist;
d_front = Q_max( dot, d_front );
d_back = Q_min( dot, d_back );
}
}
else
{
// sloping planes take longer
for( i = 0, p = w->p[0]; i < w->numpoints; i++, p += 3 )
{
dot = DotProduct( p, split->normal ) - split->dist;
d_front = Q_max( dot, d_front );
d_back = Q_min( dot, d_back );
}
}
if( d_front <= BSPCHOP_EPSILON )
{
if( epsilonsplit && ( d_front > MINSPLIT_EPSILON || d_back > -MAXSPLIT_EPSILON ))
(*epsilonsplit)++;
return SIDE_BACK;
}
if( d_back >= -BSPCHOP_EPSILON )
{
if( epsilonsplit && ( d_back < -MINSPLIT_EPSILON || d_front < MAXSPLIT_EPSILON ))
(*epsilonsplit)++;
return SIDE_FRONT;
}
if( epsilonsplit && ( d_front < MAXSPLIT_EPSILON || d_back > -MAXSPLIT_EPSILON ))
(*epsilonsplit)++;
return SIDE_ON;
}
/*
==================
ChooseMidPlaneFromList
When there are a huge number of planes, just choose one closest
to the middle.
==================
*/
surface_t *ChooseMidPlaneFromList( surface_t *surfaces, const vec3_t mins, const vec3_t maxs, int detaillevel )
{
surface_t *p, *bestsurface;
vec_t bestvalue, value;
plane_t *plane;
surftree_t *surfacetree;
vec_t dist;
face_t *f, **fp;
surfacetree = BuildSurfaceTree( surfaces, BSPCHOP_EPSILON );
// pick the plane that splits the least
bestsurface = NULL;
bestvalue = 9e30;
for( p = surfaces; p != NULL; p = p->next )
{
if( p->onnode || p->detaillevel != detaillevel )
continue;
plane = &g_mapplanes[p->planenum];
// check for axis aligned surfaces
int l = plane->type;
if( l > PLANE_LAST_AXIAL )
continue;
// calculate the split metric along axis l, smaller values are better
value = 0;
dist = plane->dist * plane->normal[l];
if( maxs[l] - dist < g_maxnode_size / 2.0 - ON_EPSILON || dist - mins[l] < g_maxnode_size / 2.0 - ON_EPSILON )
continue;
double crosscount = 0;
double frontcount = 0;
double backcount = 0;
double coplanarcount = 0;
TestSurfaceTree( surfacetree, plane );
frontcount += surfacetree->frontsize;
backcount += surfacetree->backsize;
for( fp = surfacetree->middle->array; fp && *fp != NULL; fp++ )
{
f = *fp;
if( f->facestyle == face_discardable )
continue;
if( f->planenum == p->planenum || f->planenum == ( p->planenum ^ 1 ))
{
coplanarcount++;
continue;
}
switch( FaceSide( f, plane ))
{
case SIDE_FRONT:
frontcount++;
break;
case SIDE_BACK:
backcount++;
break;
case SIDE_ON:
crosscount++;
break;
}
}
double frontsize = frontcount + 0.5 * coplanarcount + 0.5 * crosscount;
double frontfrac = (maxs[l] - dist) / (maxs[l] - mins[l]);
double backsize = backcount + 0.5 * coplanarcount + 0.5 * crosscount;
double backfrac = (dist - mins[l]) / (maxs[l] - mins[l]);
value = crosscount + 0.1 * (frontsize * (log( frontfrac ) / log( 2.0 )) + backsize * ( log( backfrac ) / log( 2.0 )));
// the first part is how the split will increase the number of faces
// the second part is how the split will increase the average depth of the bsp tree
if( value > bestvalue )
continue;
// currently the best!
bestvalue = value;
bestsurface = p;
}
DeleteSurfaceTree( surfacetree );
return bestsurface;
}
/*
==================
ChoosePlaneFromList
Choose the plane that splits the least faces
==================
*/
surface_t *ChoosePlaneFromList( surface_t *surfaces, const vec3_t mins, const vec3_t maxs, int detaillevel )
{
surface_t *p, *bestsurface;
vec_t value, bestvalue;
double totalsplit;
double avesplit;
double planecount;
surftree_t* surfacetree;
plane_t *plane;
face_t *f, **fp;
planecount = totalsplit = 0;
surfacetree = BuildSurfaceTree( surfaces, BSPCHOP_EPSILON );
// pick the plane that splits the least
bestvalue = 9e30;
bestsurface = NULL;
for( p = surfaces; p != NULL; p = p->next )
{
if( p->onnode || p->detaillevel != detaillevel )
continue;
planecount++;
double crosscount = 0;
double frontcount = 0;
double backcount = 0;
double coplanarcount = 0;
double epsilonsplit = 0;
plane = &g_mapplanes[p->planenum];
for( f = p->faces; f != NULL; f = f->next )
{
if( f->facestyle == face_discardable )
continue;
coplanarcount++;
}
TestSurfaceTree( surfacetree, plane );
frontcount += surfacetree->frontsize;
backcount += surfacetree->backsize;
for( fp = surfacetree->middle->array; fp && *fp != NULL; fp++ )
{
f = *fp;
if( f->planenum == p->planenum || f->planenum == ( p->planenum ^ 1 ))
continue;
if( f->facestyle == face_discardable )
{
FaceSide( f, plane, &epsilonsplit );
continue;
}
switch( FaceSide( f, plane, &epsilonsplit ))
{
case SIDE_FRONT:
frontcount++;
break;
case SIDE_BACK:
backcount++;
break;
case SIDE_ON:
totalsplit++;
crosscount++;
break;
}
}
value = crosscount - sqrt( coplanarcount ); // Not optimized. --vluzacn
if( coplanarcount == 0 ) crosscount += 1;
// This is the most efficient code among what I have ever tested:
// (1) BSP file is small, despite possibility of slowing down vis and rad
// (but still faster than the original non BSP balancing method).
// (2) Factors need not adjust across various maps.
double frac = (coplanarcount / 2 + crosscount / 2 + frontcount) / (coplanarcount + frontcount + backcount + crosscount);
double ent = 0.0;
if( frac > 0.0001 && frac < 0.9999 )
ent = (-frac * log( frac ) / log( 2.0 ) - (1.0 - frac) * log( 1.0 - frac ) / log( 2.0 ));
g_splitvalue[p->planenum][1] = crosscount * (1.0 - ent);
value += epsilonsplit * 10000;
g_splitvalue[p->planenum][0] = value;
}
avesplit = totalsplit / planecount;
for( p = surfaces; p != NULL; p = p->next )
{
if( p->onnode || p->detaillevel != detaillevel )
continue;
value = g_splitvalue[p->planenum][0] + avesplit * g_splitvalue[p->planenum][1];
if( value < bestvalue )
{
bestvalue = value;
bestsurface = p;
}
}
if( !bestsurface )
COM_FatalError( "ChoosePlaneFromList: no valid planes\n" );
DeleteSurfaceTree( surfacetree );
return bestsurface;
}
/*
==================
SelectPartition
Selects a surface from a linked list of surfaces to split the group on
returns NULL if the surface list can not be divided any more (a leaf)
==================
*/
surface_t *SelectPartition( surface_t *surfaces, node_t *node, bool midsplit, int splitdetaillevel, vec3_t validmins, vec3_t validmaxs )
{
surface_t *p;
if( splitdetaillevel == -1 )
return NULL;
if( midsplit )
{
// do fast way for clipping hull
if(( p = ChooseMidPlaneFromList( surfaces, validmins, validmaxs, splitdetaillevel )) != NULL )
return p;
}
// do slow way to save poly splits for drawing hull
return ChoosePlaneFromList( surfaces, node->mins, node->maxs, splitdetaillevel );
}