/** * @brief The incoming list will be freed before exiting */ tree_t *BuildTree (bspbrush_t *brushlist, const vec3_t mins, const vec3_t maxs) { node_t *node; tree_t *tree; vec3_t blmins, blmaxs; Verb_Printf(VERB_EXTRA, "--- BrushBSP ---\n"); tree = AllocTree(); ClearBounds(blmins, blmaxs); BrushlistCalcStats(brushlist, blmins, blmaxs); tree->aabb.add(blmins); tree->aabb.add(blmaxs); c_nodes = 0; c_nonvis = 0; node = AllocNode(); node->volume = BrushFromBounds(mins, maxs); tree->headnode = node; node = BuildTree_r(node, brushlist); Verb_Printf(VERB_EXTRA, "%5i visible nodes\n", c_nodes / 2 - c_nonvis); Verb_Printf(VERB_EXTRA, "%5i nonvis nodes\n", c_nonvis); Verb_Printf(VERB_EXTRA, "%5i leafs\n", (c_nodes + 1) / 2); return tree; }
/* ================ FaceBSP List will be freed before returning ================ */ tree_t *FaceBSP(bspFace_t * list) { tree_t *tree; bspFace_t *face; int i; int count; Sys_FPrintf(SYS_VRB, "--- FaceBSP ---\n"); tree = AllocTree(); count = 0; for(face = list; face; face = face->next) { count++; for(i = 0; i < face->w->numpoints; i++) { AddPointToBounds(face->w->p[i], tree->mins, tree->maxs); } } Sys_FPrintf(SYS_VRB, "%5i faces\n", count); tree->headnode = AllocNode(); VectorCopy(tree->mins, tree->headnode->mins); VectorCopy(tree->maxs, tree->headnode->maxs); c_faceLeafs = 0; BuildFaceTree_r(tree->headnode, list); Sys_FPrintf(SYS_VRB, "%5i leafs\n", c_faceLeafs); return tree; }
/* ============ ProcessSubModel ============ */ void ProcessSubModel( void ) { entity_t *e; tree_t *tree; bspbrush_t *b, *bc; node_t *node; BeginModel (); e = &entities[entity_num]; e->firstDrawSurf = numMapDrawSurfs; PatchMapDrawSurfs( e ); // just put all the brushes in an empty leaf // FIXME: patches? node = AllocNode(); node->planenum = PLANENUM_LEAF; for ( b = e->brushes ; b ; b = b->next ) { bc = CopyBrush( b ); bc->next = node->brushlist; node->brushlist = bc; } tree = AllocTree(); tree->headnode = node; ClipSidesIntoTree( e, tree ); // subdivide each drawsurf as required by shader tesselation or fog if ( !nosubdivide ) { SubdivideDrawSurfs( e, tree ); } // merge together all common shaders on the same plane and remove // all colinear points, so extra tjunctions won't be generated if ( !nomerge ) { MergeSides( e, tree ); // !@# testing } // add in any vertexes required to fix tjunctions if ( !notjunc ) { FixTJunctions( e ); } // allocate lightmaps for faces and patches AllocateLightmaps( e ); // add references to the final drawsurfs in the apropriate clusters FilterDrawsurfsIntoTree( e, tree ); EndModel ( node ); FreeTree( tree ); }
/* ================ FaceBSP List will be freed before returning ================ */ tree_t *FaceBSP(face_t * list, qboolean drawDebug) { tree_t *tree; face_t *face; int i; int count; Sys_FPrintf(SYS_VRB, "--- FaceBSP ---\n"); tree = AllocTree(); count = 0; for(face = list; face != NULL; face = face->next) { count++; for(i = 0; i < face->w->numpoints; i++) { AddPointToBounds(face->w->p[i], tree->mins, tree->maxs); } } Sys_FPrintf(SYS_VRB, "%9d faces\n", count); for(i = 0; i < nummapplanes; i++) { mapplanes[i].counter = 0; } tree->headnode = AllocNode(); VectorCopy(tree->mins, tree->headnode->mins); VectorCopy(tree->maxs, tree->headnode->maxs); c_faceLeafs = 0; c_faceNodes = 1; #if 1 if(drawBSP && drawDebug) { drawTree = tree; } #endif BuildFaceTree_r(tree->headnode, list); Sys_FPrintf(SYS_VRB, "%9d nodes\n", c_faceNodes); Sys_FPrintf(SYS_VRB, "%9d leafs\n", c_faceLeafs); Sys_FPrintf(SYS_VRB, "%9d depth\n", (int)(logf(c_faceNodes) / logf(2))); return tree; }
/* ================ FaceBSP List will be freed before returning ================ */ tree_t *FaceBSP( bspface_t *list ) { tree_t *tree; bspface_t *face; int i; int count; int start, end; start = Sys_Milliseconds(); common->Printf( "--- FaceBSP ---\n" ); tree = AllocTree (); count = 0; tree->bounds.Clear(); for ( face = list ; face ; face = face->next ) { count++; for ( i = 0 ; i < face->w->GetNumPoints() ; i++ ) { tree->bounds.AddPoint( (*face->w)[i].ToVec3() ); } } common->Printf( "%5i faces\n", count ); tree->headnode = AllocNode(); tree->headnode->bounds = tree->bounds; c_faceLeafs = 0; BuildFaceTree_r ( tree->headnode, list ); common->Printf( "%5i leafs\n", c_faceLeafs ); end = Sys_Milliseconds(); common->Printf( "%5.1f seconds faceBsp\n", ( end - start ) / 1000.0 ); return tree; }
/* ================= BrushBSP The incoming list will be freed before exiting ================= */ tree_t *BrushBSP (bspbrush_t *brushlist, Vector& mins, Vector& maxs) { node_t *node; bspbrush_t *b; int c_faces, c_nonvisfaces; int c_brushes; tree_t *tree; int i; vec_t volume; qprintf ("--- BrushBSP ---\n"); tree = AllocTree (); c_faces = 0; c_nonvisfaces = 0; c_brushes = 0; for (b=brushlist ; b ; b=b->next) { c_brushes++; volume = BrushVolume (b); if (volume < microvolume) { Warning("Brush %i: WARNING, microbrush\n", b->original->id); } for (i=0 ; i<b->numsides ; i++) { if (b->sides[i].bevel) continue; if (!b->sides[i].winding) continue; if (b->sides[i].texinfo == TEXINFO_NODE) continue; if (b->sides[i].visible) c_faces++; else c_nonvisfaces++; } AddPointToBounds (b->mins, tree->mins, tree->maxs); AddPointToBounds (b->maxs, tree->mins, tree->maxs); } qprintf ("%5i brushes\n", c_brushes); qprintf ("%5i visible faces\n", c_faces); qprintf ("%5i nonvisible faces\n", c_nonvisfaces); c_nodes = 0; c_nonvis = 0; node = AllocNode (); node->volume = BrushFromBounds (mins, maxs); tree->headnode = node; node = BuildTree_r (node, brushlist); qprintf ("%5i visible nodes\n", c_nodes/2 - c_nonvis); qprintf ("%5i nonvis nodes\n", c_nonvis); qprintf ("%5i leafs\n", (c_nodes+1)/2); #if 0 { // debug code static node_t *tnode; Vector p; p[0] = -1469; p[1] = -118; p[2] = 119; tnode = PointInLeaf (tree->headnode, p); Msg("contents: %i\n", tnode->contents); p[0] = 0; } #endif return tree; }
/* * ProcessWorldModel */ static void ProcessWorldModel(void){ entity_t *e; tree_t *tree; boolean_t leaked; boolean_t optimize; e = &entities[entity_num]; brush_start = e->first_brush; brush_end = brush_start + e->num_brushes; leaked = false; // perform per-block operations if(block_xh * 1024 > map_maxs[0]) block_xh = floor(map_maxs[0] / 1024.0); if((block_xl + 1) * 1024 < map_mins[0]) block_xl = floor(map_mins[0] / 1024.0); if(block_yh * 1024 > map_maxs[1]) block_yh = floor(map_maxs[1] / 1024.0); if((block_yl + 1) * 1024 < map_mins[1]) block_yl = floor(map_mins[1] / 1024.0); if(block_xl < -4) block_xl = -4; if(block_yl < -4) block_yl = -4; if(block_xh > 3) block_xh = 3; if(block_yh > 3) block_yh = 3; for(optimize = false; optimize <= true; optimize++){ Com_Verbose("--------------------------------------------\n"); RunThreadsOn((block_xh - block_xl + 1) * (block_yh - block_yl + 1), !verbose, ProcessBlock_Thread); // build the division tree // oversizing the blocks guarantees that all the boundaries // will also get nodes. Com_Verbose("--------------------------------------------\n"); tree = AllocTree(); tree->head_node = BlockTree(block_xl - 1, block_yl - 1, block_xh + 1, block_yh + 1); tree->mins[0] = (block_xl) * 1024; tree->mins[1] = (block_yl) * 1024; tree->mins[2] = map_mins[2] - 8; tree->maxs[0] = (block_xh + 1) * 1024; tree->maxs[1] = (block_yh + 1) * 1024; tree->maxs[2] = map_maxs[2] + 8; // perform the global operations MakeTreePortals(tree); if(FloodEntities(tree)) FillOutside(tree->head_node); else { leaked = true; LeakFile(tree); if(leaktest){ Com_Error(ERR_FATAL, "--- MAP LEAKED, ABORTING LEAKTEST ---\n"); } Com_Verbose("**** leaked ****\n"); } MarkVisibleSides(tree, brush_start, brush_end); if(noopt || leaked) break; if(!optimize){ FreeTree(tree); } } FloodAreas(tree); MakeFaces(tree->head_node); FixTjuncs(tree->head_node); if(!noprune) PruneNodes(tree->head_node); WriteBSP(tree->head_node); if(!leaked) WritePortalFile(tree); FreeTree(tree); }
void ProcessSubModel( void ) { entity_t *e; tree_t *tree; brush_t *b, *bc; node_t *node; /* start a brush model */ BeginModel(); e = &entities[ mapEntityNum ]; e->firstDrawSurf = numMapDrawSurfs; /* ydnar: gs mods */ ClearMetaTriangles(); /* check for patches with adjacent edges that need to lod together */ PatchMapDrawSurfs( e ); /* allocate a tree */ node = AllocNode(); node->planenum = PLANENUM_LEAF; tree = AllocTree(); tree->headnode = node; /* add the sides to the tree */ ClipSidesIntoTree( e, tree ); /* ydnar: create drawsurfs for triangle models */ AddTriangleModels( e ); /* create drawsurfs for surface models */ AddEntitySurfaceModels( e ); /* generate bsp brushes from map brushes */ EmitBrushes( e->brushes, &e->firstBrush, &e->numBrushes ); /* just put all the brushes in headnode */ for( b = e->brushes; b; b = b->next ) { bc = CopyBrush( b ); bc->next = node->brushlist; node->brushlist = bc; } /* subdivide each drawsurf as required by shader tesselation */ if( !nosubdivide ) SubdivideFaceSurfaces( e, tree ); /* add in any vertexes required to fix t-junctions */ if( !notjunc ) FixTJunctions( e ); /* ydnar: classify the surfaces and project lightmaps */ ClassifyEntitySurfaces( e ); /* ydnar: project decals */ MakeEntityDecals( e ); /* ydnar: meta surfaces */ MakeEntityMetaTriangles( e ); SmoothMetaTriangles(); FixMetaTJunctions(); MergeMetaTriangles(); /* add references to the final drawsurfs in the apropriate clusters */ FilterDrawsurfsIntoTree( e, tree ); /* match drawsurfaces back to original brushsides (sof2) */ FixBrushSides( e ); /* finish */ EndModel( e, node ); FreeTree( tree ); }
/* * ================= * BrushBSP * * The incoming list will be freed before exiting * ================= */ tree_t *BrushBSP(brush_t *brushlist, vec3_t mins, vec3_t maxs) { node_t *node; brush_t *b; int32_t c_faces, c_nonvisfaces; int32_t c_brushes; tree_t *tree; int32_t i; vec_t volume; Com_Debug(DEBUG_ALL, "--- BrushBSP ---\n"); tree = AllocTree(); c_faces = 0; c_nonvisfaces = 0; c_brushes = 0; for (b = brushlist; b; b = b->next) { c_brushes++; volume = BrushVolume(b); if (volume < microvolume) { Mon_SendSelect(ERROR_WARN, b->original->entity_num, b->original->brush_num, "Microbrush"); } for (i = 0; i < b->num_sides; i++) { if (b->sides[i].bevel) { continue; } if (!b->sides[i].winding) { continue; } if (b->sides[i].texinfo == TEXINFO_NODE) { continue; } if (b->sides[i].visible) { c_faces++; } else { c_nonvisfaces++; } } AddPointToBounds(b->mins, tree->mins, tree->maxs); AddPointToBounds(b->maxs, tree->mins, tree->maxs); } Com_Debug(DEBUG_ALL, "%5i brushes\n", c_brushes); Com_Debug(DEBUG_ALL, "%5i visible faces\n", c_faces); Com_Debug(DEBUG_ALL, "%5i nonvisible faces\n", c_nonvisfaces); SDL_DestroySemaphore(semaphores.vis_nodes); semaphores.vis_nodes = SDL_CreateSemaphore(0); SDL_DestroySemaphore(semaphores.nonvis_nodes); semaphores.nonvis_nodes = SDL_CreateSemaphore(0); node = AllocNode(); node->volume = BrushFromBounds(mins, maxs); tree->head_node = node; node = BuildTree_r(node, brushlist); const uint32_t vis_nodes = SDL_SemValue(semaphores.vis_nodes); const uint32_t nonvis_nodes = SDL_SemValue(semaphores.nonvis_nodes); Com_Debug(DEBUG_ALL, "%5i visible nodes\n", vis_nodes / 2 - nonvis_nodes); Com_Debug(DEBUG_ALL, "%5i nonvis nodes\n", nonvis_nodes); Com_Debug(DEBUG_ALL, "%5i leafs\n", (vis_nodes + 1) / 2); return tree; }
/* ============ ProcessWorldModel ============ */ void ProcessWorldModel (void) { entity_t *e; tree_t *tree; qboolean leaked; qboolean optimize; e = &entities[entity_num]; brush_start = e->firstbrush; brush_end = brush_start + e->numbrushes; leaked = false; // // perform per-block operations // if (block_xh * 1024 > map_maxs[0]) block_xh = floor(map_maxs[0]/1024.0); if ( (block_xl+1) * 1024 < map_mins[0]) block_xl = floor(map_mins[0]/1024.0); if (block_yh * 1024 > map_maxs[1]) block_yh = floor(map_maxs[1]/1024.0); if ( (block_yl+1) * 1024 < map_mins[1]) block_yl = floor(map_mins[1]/1024.0); if (block_xl <-4) block_xl = -4; if (block_yl <-4) block_yl = -4; if (block_xh > 3) block_xh = 3; if (block_yh > 3) block_yh = 3; for (optimize = false ; optimize <= true ; optimize++) { qprintf ("--------------------------------------------\n"); RunThreadsOnIndividual ((block_xh-block_xl+1)*(block_yh-block_yl+1), !verbose, ProcessBlock_Thread); // // build the division tree // oversizing the blocks guarantees that all the boundaries // will also get nodes. // qprintf ("--------------------------------------------\n"); tree = AllocTree (); tree->headnode = BlockTree (block_xl-1, block_yl-1, block_xh+1, block_yh+1); tree->mins[0] = (block_xl)*1024; tree->mins[1] = (block_yl)*1024; tree->mins[2] = map_mins[2] - 8; tree->maxs[0] = (block_xh+1)*1024; tree->maxs[1] = (block_yh+1)*1024; tree->maxs[2] = map_maxs[2] + 8; // // perform the global operations // MakeTreePortals (tree); if (FloodEntities (tree)) FillOutside (tree->headnode); else { printf ("**** leaked ****\n"); leaked = true; LeakFile (tree); if (leaktest) { printf ("--- MAP LEAKED ---\n"); exit (0); } } MarkVisibleSides (tree, brush_start, brush_end); if (noopt || leaked) break; if (!optimize) { FreeTree (tree); } } FloodAreas (tree); if (glview) WriteGLView (tree, source); MakeFaces (tree->headnode); FixTjuncs (tree->headnode); if (!noprune) PruneNodes (tree->headnode); WriteBSP (tree->headnode); if (!leaked) WritePortalFile (tree); FreeTree (tree); }
void ProcessWorldModel (void) { entity_t *e; tree_t *tree = NULL; qboolean leaked; int optimize; int start; e = &entities[entity_num]; brush_start = e->firstbrush; brush_end = brush_start + e->numbrushes; leaked = false; // // perform per-block operations // if (block_xh * BLOCKS_SIZE > g_MainMap->map_maxs[0]) { block_xh = floor(g_MainMap->map_maxs[0]/BLOCKS_SIZE); } if ( (block_xl+1) * BLOCKS_SIZE < g_MainMap->map_mins[0]) { block_xl = floor(g_MainMap->map_mins[0]/BLOCKS_SIZE); } if (block_yh * BLOCKS_SIZE > g_MainMap->map_maxs[1]) { block_yh = floor(g_MainMap->map_maxs[1]/BLOCKS_SIZE); } if ( (block_yl+1) * BLOCKS_SIZE < g_MainMap->map_mins[1]) { block_yl = floor(g_MainMap->map_mins[1]/BLOCKS_SIZE); } // HLTOOLS: updated to +/- MAX_COORD_INTEGER ( new world size limits / worldsize.h ) if (block_xl < BLOCKS_MIN) { block_xl = BLOCKS_MIN; } if (block_yl < BLOCKS_MIN) { block_yl = BLOCKS_MIN; } if (block_xh > BLOCKS_MAX) { block_xh = BLOCKS_MAX; } if (block_yh > BLOCKS_MAX) { block_yh = BLOCKS_MAX; } for (optimize = 0 ; optimize <= 1 ; optimize++) { qprintf ("--------------------------------------------\n"); RunThreadsOnIndividual ((block_xh-block_xl+1)*(block_yh-block_yl+1), !verbose, ProcessBlock_Thread); // // build the division tree // oversizing the blocks guarantees that all the boundaries // will also get nodes. // qprintf ("--------------------------------------------\n"); tree = AllocTree (); tree->headnode = BlockTree (block_xl-1, block_yl-1, block_xh+1, block_yh+1); tree->mins[0] = (block_xl)*BLOCKS_SIZE; tree->mins[1] = (block_yl)*BLOCKS_SIZE; tree->mins[2] = g_MainMap->map_mins[2] - 8; tree->maxs[0] = (block_xh+1)*BLOCKS_SIZE; tree->maxs[1] = (block_yh+1)*BLOCKS_SIZE; tree->maxs[2] = g_MainMap->map_maxs[2] + 8; // // perform the global operations // // make the portals/faces by traversing down to each empty leaf MakeTreePortals (tree); if (FloodEntities (tree)) { // turns everthing outside into solid FillOutside (tree->headnode); } else { Warning( ("**** leaked ****\n") ); leaked = true; LeakFile (tree); if (leaktest) { Warning( ("--- MAP LEAKED ---\n") ); exit (0); } } // mark the brush sides that actually turned into faces MarkVisibleSides (tree, brush_start, brush_end, NO_DETAIL); if (noopt || leaked) break; if (!optimize) { // If we are optimizing, free the tree. Next time we will construct it again, but // we'll use the information in MarkVisibleSides() so we'll only split with planes that // actually contribute renderable geometry FreeTree (tree); } } FloodAreas (tree); RemoveAreaPortalBrushes_R( tree->headnode ); start = Plat_FloatTime(); Msg("Building Faces..."); // this turns portals with one solid side into faces // it also subdivides each face if necessary to fit max lightmap dimensions MakeFaces (tree->headnode); Msg("done (%d)\n", (int)(Plat_FloatTime() - start) ); if (glview) { WriteGLView (tree, source); } AssignOccluderAreas( tree ); Compute3DSkyboxAreas( tree->headnode, g_SkyAreas ); face_t *pLeafFaceList = NULL; if ( !nodetail ) { pLeafFaceList = MergeDetailTree( tree, brush_start, brush_end ); } start = Plat_FloatTime(); Msg("FixTjuncs...\n"); // This unifies the vertex list for all edges (splits collinear edges to remove t-junctions) // It also welds the list of vertices out of each winding/portal and rounds nearly integer verts to integer pLeafFaceList = FixTjuncs (tree->headnode, pLeafFaceList); // this merges all of the solid nodes that have separating planes if (!noprune) { Msg("PruneNodes...\n"); PruneNodes (tree->headnode); } // Msg( "SplitSubdividedFaces...\n" ); // SplitSubdividedFaces( tree->headnode ); Msg("WriteBSP...\n"); WriteBSP (tree->headnode, pLeafFaceList); Msg("done (%d)\n", (int)(Plat_FloatTime() - start) ); if (!leaked) { WritePortalFile (tree); } FreeTree( tree ); FreeLeafFaces( pLeafFaceList ); }
int main(int argc, char** argv) { TREE pTree = AllocTree(NULL); void *pContents; ITERATOR pIter; int nChoice = 0, nKey; while (nChoice != -1) { printf("1: Insert\n"); printf("2: Remove\n"); printf("3: Print\n"); printf("4: Print all keys\n"); printf("5: Print all keys reverse\n"); printf("6: Mass insert\n"); printf("7: Mass remove\n"); printf("Else: Quit\n\n"); BTSCAN("%d", &nChoice); switch(nChoice) { case 1: printf("Key: "); BTSCAN("%d", &nKey); Insert(nKey, "temp", pTree); break; case 2: printf("Key: "); BTSCAN("%d", &nKey); Remove(nKey, pTree); break; case 3: printf("Key: " ); BTSCAN("%d", &nKey); pContents = Search(nKey, pTree); if (pContents != NULL) { printf("Contents: %s\n", pContents); } else { printf("Not found\n"); } break; case 4: pIter = AllocIterator(); Attach(pIter, pTree); while ((pContents = Next(pIter)) != NULL) { printf("Contents: %d\n", pContents); } Detach(pIter); FreeIterator(pIter); break; case 5: pIter = AllocIterator(); AttachEnd(pIter, pTree); while ((pContents = Next(pIter)) != NULL) { printf("Contents: %d\n", pContents); } Detach(pIter); FreeIterator(pIter); break; case 6: for (nKey = 100; nKey > 0; nKey--) { Insert(nKey, "temp", pTree); } break; case 7: for (nKey = 100; nKey > 0; nKey--) { Remove(nKey, pTree); } break; default: nChoice = -1; break; } printf("\n"); } return 0; }
/* PseudoCompileBSP() a stripped down ProcessModels */ void PseudoCompileBSP( qboolean need_tree, const char *BSPFilePath, const char *surfaceFilePath ){ int models; char modelValue[10]; entity_t *entity; face_t *faces; tree_t *tree; node_t *node; brush_t *brush; side_t *side; int i; SetDrawSurfacesBuffer(); mapDrawSurfs = safe_malloc( sizeof( mapDrawSurface_t ) * MAX_MAP_DRAW_SURFS ); memset( mapDrawSurfs, 0, sizeof( mapDrawSurface_t ) * MAX_MAP_DRAW_SURFS ); numMapDrawSurfs = 0; BeginBSPFile(); models = 1; for ( mapEntityNum = 0; mapEntityNum < numEntities; mapEntityNum++ ) { /* get entity */ entity = &entities[ mapEntityNum ]; if ( entity->brushes == NULL && entity->patches == NULL ) { continue; } if ( mapEntityNum != 0 ) { sprintf( modelValue, "*%d", models++ ); SetKeyValue( entity, "model", modelValue ); } /* process the model */ Sys_FPrintf( SYS_VRB, "############### model %i ###############\n", numBSPModels ); BeginModel(); entity->firstDrawSurf = numMapDrawSurfs; ClearMetaTriangles(); PatchMapDrawSurfs( entity ); if ( mapEntityNum == 0 && need_tree ) { faces = MakeStructuralBSPFaceList( entities[0].brushes ); tree = FaceBSP( faces ); node = tree->headnode; } else { node = AllocNode(); node->planenum = PLANENUM_LEAF; tree = AllocTree(); tree->headnode = node; } /* a minimized ClipSidesIntoTree */ for ( brush = entity->brushes; brush; brush = brush->next ) { /* walk the brush sides */ for ( i = 0; i < brush->numsides; i++ ) { /* get side */ side = &brush->sides[ i ]; if ( side->winding == NULL ) { continue; } /* shader? */ if ( side->shaderInfo == NULL ) { continue; } /* save this winding as a visible surface */ DrawSurfaceForSide( entity, brush, side, side->winding ); } } if ( meta ) { ClassifyEntitySurfaces( entity ); MakeEntityDecals( entity ); MakeEntityMetaTriangles( entity ); SmoothMetaTriangles(); MergeMetaTriangles(); } FilterDrawsurfsIntoTree( entity, tree ); FilterStructuralBrushesIntoTree( entity, tree ); FilterDetailBrushesIntoTree( entity, tree ); EmitBrushes( entity->brushes, &entity->firstBrush, &entity->numBrushes ); EndModel( entity, node ); } EndBSPFile( qfalse, BSPFilePath, surfaceFilePath ); }