// 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 ); } }
//----------------------------------------------------------------------------- // 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; tree_t *detailtree = NULL; face_t *pFaces = NULL; face_t *pLeafFaceList = NULL; // Grab the list of detail brushes detailbrushes = MakeBspBrushList (brush_start, brush_end, map_mins, 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; }
// 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; }
//----------------------------------------------------------------------------- // 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; }
// Returns false if union of brushes is obviously zero static void AddPlaneToUnion(brushhull_t* hull, const int planenum) { bool need_new_face = false; bface_t* new_face_list; bface_t* face; bface_t* next; plane_t* split; Winding* front; Winding* back; new_face_list = NULL; next = NULL; hlassert(hull); if (!hull->faces) { return; } hlassert(hull->faces->w); for (face = hull->faces; face; face = next) { hlassert(face->w); next = face->next; // Duplicate plane, ignore if (face->planenum == planenum) { AddFaceToList(&new_face_list, CopyFace(face)); continue; } split = &g_mapplanes[planenum]; face->w->Clip(split->normal, split->dist, &front, &back); if (front) { delete front; need_new_face = true; if (back) { // Intersected the face delete face->w; face->w = back; AddFaceToList(&new_face_list, CopyFace(face)); } } else { // Completely missed it, back is identical to face->w so it is destroyed if (back) { delete back; AddFaceToList(&new_face_list, CopyFace(face)); } } hlassert(face->w); } FreeFaceList(hull->faces); hull->faces = new_face_list; if (need_new_face && (NumberOfHullFaces(hull) > 2)) { Winding* new_winding = NewWindingFromPlane(hull, planenum); if (new_winding) { bface_t* new_face = (bface_t*)Alloc(sizeof(bface_t)); new_face->planenum = planenum; new_face->w = new_winding; new_face->next = hull->faces; hull->faces = new_face; } } }
void CalculateBrushUnions(const int brushnum) { int bn, hull; brush_t* b1; brush_t* b2; brushhull_t* bh1; brushhull_t* bh2; entity_t* e; b1 = &g_mapbrushes[brushnum]; e = &g_entities[b1->entitynum]; for (hull = 0; hull < 1 /* NUM_HULLS */ ; hull++) { bh1 = &b1->hulls[hull]; if (!bh1->faces) // Skip it if it is not in this hull { continue; } for (bn = brushnum + 1; bn < e->numbrushes; bn++) { // Only compare if b2 > b1, tests are communitive b2 = &g_mapbrushes[e->firstbrush + bn]; bh2 = &b2->hulls[hull]; if (!bh2->faces) // Skip it if it is not in this hull { continue; } if (b1->contents != b2->contents) { continue; // different contents, ignore } Developer(DEVELOPER_LEVEL_SPAM, "Processing hull %d brush %d and brush %d\n", hull, brushnum, bn); { brushhull_t union_hull; bface_t* face; union_hull.bounds = bh1->bounds; union_hull.faces = CopyFaceList(bh1->faces); for (face = bh2->faces; face; face = face->next) { AddPlaneToUnion(&union_hull, face->planenum); } // union was clipped away (no intersection) if (!union_hull.faces) { continue; } if (g_developer >= DEVELOPER_LEVEL_MESSAGE) { Log("\nUnion windings\n"); DumpHullWindings(&union_hull); Log("\nBrush %d windings\n", brushnum); DumpHullWindings(bh1); Log("\nBrush %d windings\n", bn); DumpHullWindings(bh2); } { vec_t volume_brush_1; vec_t volume_brush_2; vec_t volume_brush_union; vec_t volume_ratio_1; vec_t volume_ratio_2; if (isInvalidHull(&union_hull)) { FreeFaceList(union_hull.faces); continue; } volume_brush_union = CalculateSolidVolume(&union_hull); volume_brush_1 = CalculateSolidVolume(bh1); volume_brush_2 = CalculateSolidVolume(bh2); volume_ratio_1 = volume_brush_union / volume_brush_1; volume_ratio_2 = volume_brush_union / volume_brush_2; if ((volume_ratio_1 > g_BrushUnionThreshold) || (g_developer >= DEVELOPER_LEVEL_MESSAGE)) { volume_ratio_1 *= 100.0; Warning("Entity %d : Brush %d intersects with brush %d by %2.3f percent", #ifdef HLCSG_COUNT_NEW b1->originalentitynum, b1->originalbrushnum, b2->originalbrushnum, #else b1->entitynum, brushnum, bn, #endif volume_ratio_1); } if ((volume_ratio_2 > g_BrushUnionThreshold) || (g_developer >= DEVELOPER_LEVEL_MESSAGE)) { volume_ratio_2 *= 100.0; Warning("Entity %d : Brush %d intersects with brush %d by %2.3f percent", #ifdef HLCSG_COUNT_NEW b1->originalentitynum, b2->originalbrushnum, b1->originalbrushnum, #else b1->entitynum, bn, brushnum, #endif volume_ratio_2); } } FreeFaceList(union_hull.faces); } } } }