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.
693 lines
18 KiB
693 lines
18 KiB
//========= Copyright Valve Corporation, All rights reserved. ============// |
|
// |
|
// Purpose: Builds/merges the BSP tree of detail brushes |
|
// |
|
// $NoKeywords: $ |
|
//=============================================================================// |
|
|
|
#include "vbsp.h" |
|
#include "detail.h" |
|
#include "utlvector.h" |
|
#include <assert.h> |
|
|
|
face_t *NewFaceFromFace (face_t *f); |
|
face_t *ComputeVisibleBrushSides( bspbrush_t *list ); |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Copies a face and its winding |
|
// Input : *pFace - |
|
// Output : face_t |
|
//----------------------------------------------------------------------------- |
|
face_t *CopyFace( face_t *pFace ) |
|
{ |
|
face_t *f = NewFaceFromFace( pFace ); |
|
f->w = CopyWinding( pFace->w ); |
|
|
|
return f; |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Link this brush into the list for this leaf |
|
// Input : *node - |
|
// *brush - |
|
//----------------------------------------------------------------------------- |
|
void AddBrushToLeaf( node_t *node, bspbrush_t *brush ) |
|
{ |
|
brush->next = node->brushlist; |
|
node->brushlist = brush; |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Recursively filter a brush through the tree |
|
// Input : *node - |
|
// *brush - |
|
//----------------------------------------------------------------------------- |
|
void MergeBrush_r( node_t *node, bspbrush_t *brush ) |
|
{ |
|
if ( node->planenum == PLANENUM_LEAF ) |
|
{ |
|
if ( node->contents & CONTENTS_SOLID ) |
|
{ |
|
FreeBrush( brush ); |
|
} |
|
else |
|
{ |
|
AddBrushToLeaf( node, brush ); |
|
} |
|
return; |
|
} |
|
|
|
bspbrush_t *front, *back; |
|
SplitBrush( brush, node->planenum, &front, &back ); |
|
FreeBrush( brush ); |
|
|
|
if ( front ) |
|
{ |
|
MergeBrush_r( node->children[0], front ); |
|
} |
|
if ( back ) |
|
{ |
|
MergeBrush_r( node->children[1], back ); |
|
} |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Recursively filter a face into the tree leaving references to the |
|
// original face in any visible leaves that a clipped fragment falls |
|
// into. |
|
// Input : *node - current head of tree |
|
// *face - clipped face fragment |
|
// *original - unclipped original face |
|
// Output : Returns true if any references were left |
|
//----------------------------------------------------------------------------- |
|
bool MergeFace_r( node_t *node, face_t *face, face_t *original ) |
|
{ |
|
bool referenced = false; |
|
|
|
if ( node->planenum == PLANENUM_LEAF ) |
|
{ |
|
if ( node->contents & CONTENTS_SOLID ) |
|
{ |
|
FreeFace( face ); |
|
return false; |
|
} |
|
|
|
leafface_t *plist = new leafface_t; |
|
plist->pFace = original; |
|
plist->pNext = node->leaffacelist; |
|
node->leaffacelist = plist; |
|
|
|
referenced = true; |
|
} |
|
else |
|
{ |
|
// UNDONE: Don't copy the faces each time unless it's necessary!?!?! |
|
plane_t *plane = &g_MainMap->mapplanes[node->planenum]; |
|
winding_t *frontwinding, *backwinding, *onwinding; |
|
|
|
Vector offset; |
|
WindingCenter( face->w, offset ); |
|
|
|
// UNDONE: Export epsilon from original face clipping code |
|
ClassifyWindingEpsilon_Offset(face->w, plane->normal, plane->dist, 0.001, &frontwinding, &backwinding, &onwinding, -offset); |
|
|
|
if ( onwinding ) |
|
{ |
|
// face is in the split plane, go down the appropriate side according to the facing direction |
|
assert( frontwinding == NULL ); |
|
assert( backwinding == NULL ); |
|
|
|
if ( DotProduct( g_MainMap->mapplanes[face->planenum].normal, g_MainMap->mapplanes[node->planenum].normal ) > 0 ) |
|
{ |
|
frontwinding = onwinding; |
|
} |
|
else |
|
{ |
|
backwinding = onwinding; |
|
} |
|
} |
|
|
|
if ( frontwinding ) |
|
{ |
|
face_t *tmp = NewFaceFromFace( face ); |
|
tmp->w = frontwinding; |
|
referenced = MergeFace_r( node->children[0], tmp, original ); |
|
} |
|
if ( backwinding ) |
|
{ |
|
face_t *tmp = NewFaceFromFace( face ); |
|
tmp->w = backwinding; |
|
bool test = MergeFace_r( node->children[1], tmp, original ); |
|
referenced = referenced || test; |
|
} |
|
} |
|
FreeFace( face ); |
|
|
|
return referenced; |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Loop through each face and filter it into the tree |
|
// Input : *out - |
|
// *pFaces - |
|
//----------------------------------------------------------------------------- |
|
face_t *FilterFacesIntoTree( tree_t *out, face_t *pFaces ) |
|
{ |
|
face_t *pLeafFaceList = NULL; |
|
for ( face_t *f = pFaces; f; f = f->next ) |
|
{ |
|
if( f->merged || f->split[0] || f->split[1] ) |
|
continue; |
|
|
|
face_t *tmp = CopyFace( f ); |
|
face_t *original = CopyFace( f ); |
|
|
|
if ( MergeFace_r( out->headnode, tmp, original ) ) |
|
{ |
|
// clear out portal (comes from a different tree) |
|
original->portal = NULL; |
|
original->next = pLeafFaceList; |
|
pLeafFaceList = original; |
|
} |
|
else |
|
{ |
|
FreeFace( original ); |
|
} |
|
} |
|
|
|
return pLeafFaceList; |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Splits the face list into faces from the same plane and tries to merge |
|
// them if possible |
|
// Input : **pFaceList - |
|
//----------------------------------------------------------------------------- |
|
void TryMergeFaceList( face_t **pFaceList ) |
|
{ |
|
face_t **pPlaneList = NULL; |
|
|
|
// divide the list into buckets by plane number |
|
pPlaneList = new face_t *[g_MainMap->nummapplanes]; |
|
memset( pPlaneList, 0, sizeof(face_t *) * g_MainMap->nummapplanes ); |
|
|
|
face_t *pFaces = *pFaceList; |
|
face_t *pOutput = NULL; |
|
|
|
while ( pFaces ) |
|
{ |
|
face_t *next = pFaces->next; |
|
|
|
// go ahead and delete the old split/merged faces |
|
if ( pFaces->merged || pFaces->split[0] || pFaces->split[1] ) |
|
{ |
|
Error("Split face in merge list!"); |
|
} |
|
else |
|
{ |
|
// add to the list for this plane |
|
pFaces->next = pPlaneList[pFaces->planenum]; |
|
pPlaneList[pFaces->planenum] = pFaces; |
|
} |
|
|
|
pFaces = next; |
|
} |
|
|
|
// now merge each plane's list of faces |
|
int merged = 0; |
|
for ( int i = 0; i < g_MainMap->nummapplanes; i++ ) |
|
{ |
|
if ( pPlaneList[i] ) |
|
{ |
|
MergeFaceList( &pPlaneList[i] ); |
|
} |
|
|
|
// move these over to the output face list |
|
face_t *list = pPlaneList[i]; |
|
while ( list ) |
|
{ |
|
face_t *next = list->next; |
|
|
|
if ( list->merged ) |
|
merged++; |
|
|
|
list->next = pOutput; |
|
pOutput = list; |
|
list = next; |
|
} |
|
} |
|
|
|
if ( merged ) |
|
{ |
|
Msg("\nMerged %d detail faces...", merged ); |
|
} |
|
delete[] pPlaneList; |
|
|
|
*pFaceList = pOutput; |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: filter each brush in the list into the tree |
|
// Input : *out - |
|
// *brushes - |
|
//----------------------------------------------------------------------------- |
|
void FilterBrushesIntoTree( tree_t *out, bspbrush_t *brushes ) |
|
{ |
|
// Merge all of the brushes into the world tree |
|
for ( bspbrush_t *plist = brushes; plist; plist = plist->next ) |
|
{ |
|
MergeBrush_r( out->headnode, CopyBrush(plist) ); |
|
} |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Build faces for the detail brushes and merge them into the BSP |
|
// Input : *worldtree - |
|
// brush_start - |
|
// brush_end - |
|
//----------------------------------------------------------------------------- |
|
face_t *MergeDetailTree( tree_t *worldtree, int brush_start, int brush_end ) |
|
{ |
|
int start; |
|
bspbrush_t *detailbrushes = NULL; |
|
face_t *pFaces = NULL; |
|
face_t *pLeafFaceList = NULL; |
|
|
|
// Grab the list of detail brushes |
|
detailbrushes = MakeBspBrushList (brush_start, brush_end, g_MainMap->map_mins, g_MainMap->map_maxs, ONLY_DETAIL ); |
|
if (detailbrushes) |
|
{ |
|
start = Plat_FloatTime(); |
|
Msg("Chop Details..."); |
|
// if there are detail brushes, chop them against each other |
|
if (!nocsg) |
|
detailbrushes = ChopBrushes (detailbrushes); |
|
|
|
Msg("done (%d)\n", (int)(Plat_FloatTime() - start) ); |
|
// Now mark the visible sides so we can eliminate all detail brush sides |
|
// that are covered by other detail brush sides |
|
// NOTE: This still leaves detail brush sides that are covered by the world. (these are removed in the merge operation) |
|
Msg("Find Visible Detail Sides..."); |
|
pFaces = ComputeVisibleBrushSides( detailbrushes ); |
|
TryMergeFaceList( &pFaces ); |
|
SubdivideFaceList( &pFaces ); |
|
Msg("done (%d)\n", (int)(Plat_FloatTime() - start) ); |
|
|
|
start = Plat_FloatTime(); |
|
Msg("Merging details..."); |
|
// Merge the detail solids and faces into the world tree |
|
// Merge all of the faces into the world tree |
|
pLeafFaceList = FilterFacesIntoTree( worldtree, pFaces ); |
|
FilterBrushesIntoTree( worldtree, detailbrushes ); |
|
|
|
FreeFaceList( pFaces ); |
|
FreeBrushList(detailbrushes); |
|
|
|
Msg("done (%d)\n", (int)(Plat_FloatTime() - start) ); |
|
} |
|
|
|
return pLeafFaceList; |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Quick overlap test for brushes |
|
// Input : *p1 - |
|
// *p2 - |
|
// Output : Returns false if the brushes cannot intersect |
|
//----------------------------------------------------------------------------- |
|
bool BrushBoxOverlap( bspbrush_t *p1, bspbrush_t *p2 ) |
|
{ |
|
if ( p1 == p2 ) |
|
return false; |
|
|
|
for ( int i = 0; i < 3; i++ ) |
|
{ |
|
if ( p1->mins[i] > p2->maxs[i] || p1->maxs[i] < p2->mins[i] ) |
|
return false; |
|
} |
|
|
|
return true; |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
// Input : *pFace - input face to test |
|
// *pbrush - brush to clip face against |
|
// **pOutputList - list of faces clipped from pFace |
|
// Output : Returns true if the brush completely clips the face |
|
//----------------------------------------------------------------------------- |
|
// NOTE: This assumes the brushes have already been chopped so that no solid space |
|
// is enclosed by more than one brush!! |
|
bool ClipFaceToBrush( face_t *pFace, bspbrush_t *pbrush, face_t **pOutputList ) |
|
{ |
|
int planenum = pFace->planenum & (~1); |
|
int foundSide = -1; |
|
|
|
CUtlVector<int> sortedSides; |
|
|
|
int i; |
|
for ( i = 0; i < pbrush->numsides && foundSide < 0; i++ ) |
|
{ |
|
int bplane = pbrush->sides[i].planenum & (~1); |
|
if ( bplane == planenum ) |
|
foundSide = i; |
|
} |
|
|
|
Vector offset = -0.5f * (pbrush->maxs + pbrush->mins); |
|
face_t *currentface = CopyFace( pFace ); |
|
|
|
if ( foundSide >= 0 ) |
|
{ |
|
sortedSides.RemoveAll(); |
|
for ( i = 0; i < pbrush->numsides; i++ ) |
|
{ |
|
// don't clip to bevels |
|
if ( pbrush->sides[i].bevel ) |
|
continue; |
|
|
|
if ( g_MainMap->mapplanes[pbrush->sides[i].planenum].type <= PLANE_Z ) |
|
{ |
|
sortedSides.AddToHead( i ); |
|
} |
|
else |
|
{ |
|
sortedSides.AddToTail( i ); |
|
} |
|
} |
|
|
|
for ( i = 0; i < sortedSides.Size(); i++ ) |
|
{ |
|
int index = sortedSides[i]; |
|
if ( index == foundSide ) |
|
continue; |
|
|
|
plane_t *plane = &g_MainMap->mapplanes[pbrush->sides[index].planenum]; |
|
winding_t *frontwinding, *backwinding; |
|
ClipWindingEpsilon_Offset(currentface->w, plane->normal, plane->dist, 0.001, &frontwinding, &backwinding, offset); |
|
|
|
// only clip if some part of this face is on the back side of all brush sides |
|
if ( !backwinding || WindingIsTiny(backwinding)) |
|
{ |
|
FreeFaceList( *pOutputList ); |
|
*pOutputList = NULL; |
|
break; |
|
} |
|
if ( frontwinding && !WindingIsTiny(frontwinding) ) |
|
{ |
|
// add this fragment to the return list |
|
// make a face for the fragment |
|
face_t *f = NewFaceFromFace( pFace ); |
|
f->w = frontwinding; |
|
|
|
// link the fragment in |
|
f->next = *pOutputList; |
|
*pOutputList = f; |
|
} |
|
|
|
// update the current winding to be the part behind each plane |
|
FreeWinding( currentface->w ); |
|
currentface->w = backwinding; |
|
} |
|
|
|
// free the bit that is left in solid or not clipped (if we broke out early) |
|
FreeFace( currentface ); |
|
|
|
// if we made it all the way through and didn't produce any fragments then the whole face was clipped away |
|
if ( !*pOutputList && i == sortedSides.Size() ) |
|
{ |
|
return true; |
|
} |
|
} |
|
return false; |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Given an original side and chopped winding, make a face_t |
|
// Input : *side - side of the original brush |
|
// *winding - winding for this face (portion of the side) |
|
// Output : face_t |
|
//----------------------------------------------------------------------------- |
|
face_t *MakeBrushFace( side_t *originalSide, winding_t *winding ) |
|
{ |
|
face_t *f = AllocFace(); |
|
f->merged = NULL; |
|
f->split[0] = f->split[1] = NULL; |
|
f->w = CopyWinding( winding ); |
|
f->originalface = originalSide; |
|
// |
|
// save material info |
|
// |
|
f->texinfo = originalSide->texinfo; |
|
f->dispinfo = -1; |
|
|
|
// save plane info |
|
f->planenum = originalSide->planenum; |
|
f->contents = originalSide->contents; |
|
|
|
return f; |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Chop away sides that are inside other brushes. |
|
// Brushes have already been chopped up so that they do not overlap, |
|
// they merely touch. |
|
// Input : *list - list of brushes |
|
// Output : face_t * - list of visible faces (some marked bad/split) |
|
//----------------------------------------------------------------------------- |
|
// assumes brushes were chopped! |
|
|
|
|
|
side_t *FindOriginalSide( mapbrush_t *mb, side_t *pBspSide ) |
|
{ |
|
side_t *bestside = NULL; |
|
float bestdot = 0; |
|
|
|
plane_t *p1 = g_MainMap->mapplanes + pBspSide->planenum; |
|
|
|
for (int i=0 ; i<mb->numsides ; i++) |
|
{ |
|
side_t *side = &mb->original_sides[i]; |
|
if (side->bevel) |
|
continue; |
|
if (side->texinfo == TEXINFO_NODE) |
|
continue; // non-visible |
|
if ((side->planenum&~1) == (pBspSide->planenum&~1)) |
|
{ // exact match |
|
return mb->original_sides + i; |
|
} |
|
// see how close the match is |
|
plane_t *p2 = &g_MainMap->mapplanes[side->planenum&~1]; |
|
float dot = DotProduct (p1->normal, p2->normal); |
|
if (dot > bestdot) |
|
{ |
|
bestdot = dot; |
|
bestside = side; |
|
} |
|
} |
|
|
|
if ( !bestside ) |
|
{ |
|
Error( "Bad detail brush side\n" ); |
|
} |
|
return bestside; |
|
} |
|
|
|
// Get a list of brushes from pBrushList that could cut faces on the source brush |
|
int GetListOfCutBrushes( CUtlVector<bspbrush_t *> &out, bspbrush_t *pSourceBrush, bspbrush_t *pBrushList ) |
|
{ |
|
mapbrush_t *mb = pSourceBrush->original; |
|
for ( bspbrush_t *walk = pBrushList; walk; walk = walk->next ) |
|
{ |
|
if ( walk == pSourceBrush ) |
|
continue; |
|
|
|
// only clip to transparent brushes if the original brush is transparent |
|
if ( walk->original->contents & TRANSPARENT_CONTENTS ) |
|
{ |
|
if ( !(mb->contents & TRANSPARENT_CONTENTS) ) |
|
continue; |
|
} |
|
|
|
// don't clip to clip brushes, etc. |
|
if ( !(walk->original->contents & ALL_VISIBLE_CONTENTS) ) |
|
continue; |
|
|
|
// brushes overlap, test faces |
|
if ( !BrushBoxOverlap( pSourceBrush, walk ) ) |
|
continue; |
|
|
|
out.AddToTail( walk ); |
|
} |
|
return out.Count(); |
|
} |
|
|
|
// Count the number of real (unsplit) faces in the list |
|
static int CountFaceList( face_t *f ) |
|
{ |
|
int count = 0; |
|
for ( ; f; f = f->next ) |
|
{ |
|
if ( f->split[0] ) |
|
continue; |
|
count++; |
|
} |
|
|
|
return count; |
|
} |
|
|
|
// Clips f to a list of potential cutting brushes |
|
// If f clips into new faces, returns the list of new faces in pOutputList |
|
static void ClipFaceToBrushList( face_t *f, const CUtlVector<bspbrush_t *> &cutBrushes, face_t **pOutputList ) |
|
{ |
|
*pOutputList = NULL; |
|
|
|
if ( f->split[0] ) |
|
return; |
|
|
|
face_t *pClipList = CopyFace( f ); |
|
pClipList->next = NULL; |
|
bool clipped = false; |
|
for ( int i = 0; i < cutBrushes.Count(); i++ ) |
|
{ |
|
bspbrush_t *cut = cutBrushes[i]; |
|
for ( face_t *pCutFace = pClipList; pCutFace; pCutFace = pCutFace->next ) |
|
{ |
|
face_t *pClip = NULL; |
|
// already split, no need to clip |
|
if ( pCutFace->split[0] ) |
|
continue; |
|
|
|
if ( ClipFaceToBrush( pCutFace, cut, &pClip ) ) |
|
{ |
|
clipped = true; |
|
// mark face bad, the brush clipped it away |
|
pCutFace->split[0] = pCutFace; |
|
} |
|
else if ( pClip ) |
|
{ |
|
clipped = true; |
|
// mark this face as split |
|
pCutFace->split[0] = pCutFace; |
|
|
|
// insert face fragments at head of list (UNDONE: reverses order, do we care?) |
|
while ( pClip ) |
|
{ |
|
face_t *next = pClip->next; |
|
pClip->next = pClipList; |
|
pClipList = pClip; |
|
pClip = next; |
|
} |
|
} |
|
} |
|
} |
|
if ( clipped ) |
|
{ |
|
*pOutputList = pClipList; |
|
} |
|
else |
|
{ |
|
// didn't do any clipping, go ahead and free the copy of the face here. |
|
FreeFaceList( pClipList ); |
|
} |
|
} |
|
|
|
// Compute a list of faces that are visible on the detail brush sides |
|
face_t *ComputeVisibleBrushSides( bspbrush_t *list ) |
|
{ |
|
face_t *pTotalFaces = NULL; |
|
CUtlVector<bspbrush_t *> cutBrushes; |
|
|
|
// Go through the whole brush list |
|
for ( bspbrush_t *pbrush = list; pbrush; pbrush = pbrush->next ) |
|
{ |
|
face_t *pFaces = NULL; |
|
mapbrush_t *mb = pbrush->original; |
|
|
|
if ( !(mb->contents & ALL_VISIBLE_CONTENTS) ) |
|
continue; |
|
|
|
// Make a face for each brush side, then clip it by the other |
|
// details to see if any fragments are visible |
|
for ( int i = 0; i < pbrush->numsides; i++ ) |
|
{ |
|
winding_t *winding = pbrush->sides[i].winding; |
|
if ( !winding ) |
|
continue; |
|
|
|
if (! (pbrush->sides[i].contents & ALL_VISIBLE_CONTENTS) ) |
|
continue; |
|
|
|
side_t *side = FindOriginalSide( mb, pbrush->sides + i ); |
|
face_t *f = MakeBrushFace( side, winding ); |
|
|
|
// link to head of face list |
|
f->next = pFaces; |
|
pFaces = f; |
|
} |
|
|
|
// Make a list of brushes that can cut the face list for this brush |
|
cutBrushes.RemoveAll(); |
|
if ( GetListOfCutBrushes( cutBrushes, pbrush, list ) ) |
|
{ |
|
// now cut each face to find visible fragments |
|
for ( face_t *f = pFaces; f; f = f->next ) |
|
{ |
|
// this will be a new list of faces that this face cuts into |
|
face_t *pClip = NULL; |
|
ClipFaceToBrushList( f, cutBrushes, &pClip ); |
|
if ( pClip ) |
|
{ |
|
int outCount = CountFaceList(pClip); |
|
// it cut into more faces (or it was completely cut away) |
|
if ( outCount <= 1 ) |
|
{ |
|
// was removed or cut down, mark as split |
|
f->split[0] = f; |
|
// insert face fragments at head of list (UNDONE: reverses order, do we care?) |
|
while ( pClip ) |
|
{ |
|
face_t *next = pClip->next; |
|
pClip->next = pFaces; |
|
pFaces = pClip; |
|
pClip = next; |
|
} |
|
} |
|
else |
|
{ |
|
// it cut into more than one visible fragment |
|
// Don't fragment details |
|
// UNDONE: Build 2d convex hull of this list and swap face winding |
|
// with that polygon? That would fix the remaining issues. |
|
FreeFaceList( pClip ); |
|
pClip = NULL; |
|
} |
|
} |
|
} |
|
} |
|
|
|
// move visible fragments to global face list |
|
while ( pFaces ) |
|
{ |
|
face_t *next = pFaces->next; |
|
if ( pFaces->split[0] ) |
|
{ |
|
FreeFace( pFaces ); |
|
} |
|
else |
|
{ |
|
pFaces->next = pTotalFaces; |
|
pTotalFaces = pFaces; |
|
} |
|
pFaces = next; |
|
} |
|
} |
|
|
|
return pTotalFaces; |
|
}
|
|
|