|
|
//========= Copyright Valve Corporation, All rights reserved. ============//
//
// Purpose: BSP Building tool
//
// $NoKeywords: $
//=============================================================================//
#include "vbsp.h"
#include "detail.h"
#include "physdll.h"
#include "utilmatlib.h"
#include "disp_vbsp.h"
#include "writebsp.h"
#include "tier0/icommandline.h"
#include "materialsystem/imaterialsystem.h"
#include "map.h"
#include "tools_minidump.h"
#include "materialsub.h"
#include "loadcmdline.h"
#include "byteswap.h"
#include "worldvertextransitionfixup.h"
extern float g_maxLightmapDimension;
char source[1024]; char mapbase[ 64 ]; char name[1024]; char materialPath[1024];
vec_t microvolume = 1.0; qboolean noprune; qboolean glview; qboolean nodetail; qboolean fulldetail; qboolean onlyents; bool onlyprops; qboolean nomerge; qboolean nomergewater = false; qboolean nowater; qboolean nocsg; qboolean noweld; qboolean noshare; qboolean nosubdiv; qboolean notjunc; qboolean noopt; qboolean leaktest; qboolean verboseentities; qboolean dumpcollide = false; qboolean g_bLowPriority = false; qboolean g_DumpStaticProps = false; qboolean g_bSkyVis = false; // skybox vis is off by default, toggle this to enable it
bool g_bLightIfMissing = false; bool g_snapAxialPlanes = false; bool g_bKeepStaleZip = false; bool g_NodrawTriggers = false; bool g_DisableWaterLighting = false; bool g_bAllowDetailCracks = false; bool g_bNoVirtualMesh = false;
float g_defaultLuxelSize = DEFAULT_LUXEL_SIZE; float g_luxelScale = 1.0f; float g_minLuxelScale = 1.0f; bool g_BumpAll = false;
int g_nDXLevel = 0; // default dxlevel if you don't specify it on the command-line.
CUtlVector<int> g_SkyAreas; char outbase[32];
char g_szEmbedDir[MAX_PATH] = { 0 };
// HLTOOLS: Introduce these calcs to make the block algorithm proportional to the proper
// world coordinate extents. Assumes square spatial constraints.
#define BLOCKS_SIZE 1024
#define BLOCKS_SPACE (COORD_EXTENT/BLOCKS_SIZE)
#define BLOCKX_OFFSET ((BLOCKS_SPACE/2)+1)
#define BLOCKY_OFFSET ((BLOCKS_SPACE/2)+1)
#define BLOCKS_MIN (-(BLOCKS_SPACE/2))
#define BLOCKS_MAX ((BLOCKS_SPACE/2)-1)
int block_xl = BLOCKS_MIN, block_xh = BLOCKS_MAX, block_yl = BLOCKS_MIN, block_yh = BLOCKS_MAX;
int entity_num;
node_t *block_nodes[BLOCKS_SPACE+2][BLOCKS_SPACE+2];
//-----------------------------------------------------------------------------
// Assign occluder areas (must happen *after* the world model is processed)
//-----------------------------------------------------------------------------
void AssignOccluderAreas( tree_t *pTree ); static void Compute3DSkyboxAreas( node_t *headnode, CUtlVector<int>& areas );
/*
============ BlockTree
============ */ node_t *BlockTree (int xl, int yl, int xh, int yh) { node_t *node; Vector normal; float dist; int mid;
if (xl == xh && yl == yh) { node = block_nodes[xl+BLOCKX_OFFSET][yl+BLOCKY_OFFSET]; if (!node) { // return an empty leaf
node = AllocNode (); node->planenum = PLANENUM_LEAF; node->contents = 0; //CONTENTS_SOLID;
return node; } return node; }
// create a seperator along the largest axis
node = AllocNode ();
if (xh - xl > yh - yl) { // split x axis
mid = xl + (xh-xl)/2 + 1; normal[0] = 1; normal[1] = 0; normal[2] = 0; dist = mid*BLOCKS_SIZE; node->planenum = g_MainMap->FindFloatPlane (normal, dist); node->children[0] = BlockTree ( mid, yl, xh, yh); node->children[1] = BlockTree ( xl, yl, mid-1, yh); } else { mid = yl + (yh-yl)/2 + 1; normal[0] = 0; normal[1] = 1; normal[2] = 0; dist = mid*BLOCKS_SIZE; node->planenum = g_MainMap->FindFloatPlane (normal, dist); node->children[0] = BlockTree ( xl, mid, xh, yh); node->children[1] = BlockTree ( xl, yl, xh, mid-1); }
return node; }
/*
============ ProcessBlock_Thread
============ */ int brush_start, brush_end; void ProcessBlock_Thread (int threadnum, int blocknum) { int xblock, yblock; Vector mins, maxs; bspbrush_t *brushes; tree_t *tree; node_t *node;
yblock = block_yl + blocknum / (block_xh-block_xl+1); xblock = block_xl + blocknum % (block_xh-block_xl+1);
qprintf ("############### block %2i,%2i ###############\n", xblock, yblock);
mins[0] = xblock*BLOCKS_SIZE; mins[1] = yblock*BLOCKS_SIZE; mins[2] = MIN_COORD_INTEGER; maxs[0] = (xblock+1)*BLOCKS_SIZE; maxs[1] = (yblock+1)*BLOCKS_SIZE; maxs[2] = MAX_COORD_INTEGER;
// the makelist and chopbrushes could be cached between the passes...
brushes = MakeBspBrushList (brush_start, brush_end, mins, maxs, NO_DETAIL); if (!brushes) { node = AllocNode (); node->planenum = PLANENUM_LEAF; node->contents = CONTENTS_SOLID; block_nodes[xblock+BLOCKX_OFFSET][yblock+BLOCKY_OFFSET] = node; return; }
FixupAreaportalWaterBrushes( brushes ); if (!nocsg) brushes = ChopBrushes (brushes);
tree = BrushBSP (brushes, mins, maxs); block_nodes[xblock+BLOCKX_OFFSET][yblock+BLOCKY_OFFSET] = tree->headnode; }
/*
============ ProcessWorldModel
============ */ void SplitSubdividedFaces( node_t *headnode ); // garymcthack
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 ); }
/*
============ ProcessSubModel
============ */ void ProcessSubModel( ) { entity_t *e; int start, end; tree_t *tree; bspbrush_t *list; Vector mins, maxs;
e = &entities[entity_num];
start = e->firstbrush; end = start + e->numbrushes;
mins[0] = mins[1] = mins[2] = MIN_COORD_INTEGER; maxs[0] = maxs[1] = maxs[2] = MAX_COORD_INTEGER; list = MakeBspBrushList (start, end, mins, maxs, FULL_DETAIL);
if (!nocsg) list = ChopBrushes (list); tree = BrushBSP (list, mins, maxs); // This would wind up crashing the engine because we'd have a negative leaf index in dmodel_t::headnode.
if ( tree->headnode->planenum == PLANENUM_LEAF ) { const char *pClassName = ValueForKey( e, "classname" ); const char *pTargetName = ValueForKey( e, "targetname" ); Error( "bmodel %d has no head node (class '%s', targetname '%s')", entity_num, pClassName, pTargetName ); }
MakeTreePortals (tree); #if DEBUG_BRUSHMODEL
if ( entity_num == DEBUG_BRUSHMODEL ) WriteGLView( tree, "tree_all" ); #endif
MarkVisibleSides (tree, start, end, FULL_DETAIL); MakeFaces (tree->headnode);
FixTjuncs( tree->headnode, NULL ); WriteBSP( tree->headnode, NULL ); #if DEBUG_BRUSHMODEL
if ( entity_num == DEBUG_BRUSHMODEL ) { WriteGLView( tree, "tree_vis" ); WriteGLViewFaces( tree, "tree_faces" ); } #endif
FreeTree (tree); }
//-----------------------------------------------------------------------------
// Returns true if the entity is a func_occluder
//-----------------------------------------------------------------------------
bool IsFuncOccluder( int entity_num ) { entity_t *mapent = &entities[entity_num]; const char *pClassName = ValueForKey( mapent, "classname" ); return (strcmp("func_occluder", pClassName) == 0); }
//-----------------------------------------------------------------------------
// Computes the area of a brush's occluders
//-----------------------------------------------------------------------------
float ComputeOccluderBrushArea( mapbrush_t *pBrush ) { float flArea = 0.0f; for ( int j = 0; j < pBrush->numsides; ++j ) { side_t *pSide = &(pBrush->original_sides[j]);
// Skip nodraw surfaces
if ( texinfo[pSide->texinfo].flags & SURF_NODRAW ) continue;
if ( !pSide->winding ) continue;
flArea += WindingArea( pSide->winding ); }
return flArea; }
//-----------------------------------------------------------------------------
// Clips all occluder brushes against each other
//-----------------------------------------------------------------------------
static tree_t *ClipOccluderBrushes( ) { // Create a list of all occluder brushes in the level
CUtlVector< mapbrush_t * > mapBrushes( 1024, 1024 ); for ( entity_num=0; entity_num < g_MainMap->num_entities; ++entity_num ) { if (!IsFuncOccluder(entity_num)) continue;
entity_t *e = &entities[entity_num]; int end = e->firstbrush + e->numbrushes; int i; for ( i = e->firstbrush; i < end; ++i ) { mapBrushes.AddToTail( &g_MainMap->mapbrushes[i] ); } }
int nBrushCount = mapBrushes.Count(); if ( nBrushCount == 0 ) return NULL;
Vector mins, maxs; mins[0] = mins[1] = mins[2] = MIN_COORD_INTEGER; maxs[0] = maxs[1] = maxs[2] = MAX_COORD_INTEGER;
bspbrush_t *list = MakeBspBrushList( mapBrushes.Base(), nBrushCount, mins, maxs );
if (!nocsg) list = ChopBrushes (list); tree_t *tree = BrushBSP (list, mins, maxs); MakeTreePortals (tree); MarkVisibleSides (tree, mapBrushes.Base(), nBrushCount); MakeFaces( tree->headnode );
// NOTE: This will output the occluder face vertices + planes
FixTjuncs( tree->headnode, NULL );
return tree; }
//-----------------------------------------------------------------------------
// Generate a list of unique sides in the occluder tree
//-----------------------------------------------------------------------------
static void GenerateOccluderSideList( int nEntity, CUtlVector<side_t*> &occluderSides ) { entity_t *e = &entities[nEntity]; int end = e->firstbrush + e->numbrushes; int i, j; for ( i = e->firstbrush; i < end; ++i ) { mapbrush_t *mb = &g_MainMap->mapbrushes[i]; for ( j = 0; j < mb->numsides; ++j ) { occluderSides.AddToTail( &(mb->original_sides[j]) ); } } }
//-----------------------------------------------------------------------------
// Generate a list of unique faces in the occluder tree
//-----------------------------------------------------------------------------
static void GenerateOccluderFaceList( node_t *pOccluderNode, CUtlVector<face_t*> &occluderFaces ) { if (pOccluderNode->planenum == PLANENUM_LEAF) return;
for ( face_t *f=pOccluderNode->faces ; f ; f = f->next ) { occluderFaces.AddToTail( f ); }
GenerateOccluderFaceList( pOccluderNode->children[0], occluderFaces ); GenerateOccluderFaceList( pOccluderNode->children[1], occluderFaces ); }
//-----------------------------------------------------------------------------
// For occluder area assignment
//-----------------------------------------------------------------------------
struct OccluderInfo_t { int m_nOccluderEntityIndex; };
static CUtlVector< OccluderInfo_t > g_OccluderInfo;
//-----------------------------------------------------------------------------
// Emits occluder brushes
//-----------------------------------------------------------------------------
static void EmitOccluderBrushes() { char str[64];
g_OccluderData.RemoveAll(); g_OccluderPolyData.RemoveAll(); g_OccluderVertexIndices.RemoveAll();
tree_t *pOccluderTree = ClipOccluderBrushes(); if (!pOccluderTree) return;
CUtlVector<face_t*> faceList( 1024, 1024 ); CUtlVector<side_t*> sideList( 1024, 1024 ); GenerateOccluderFaceList( pOccluderTree->headnode, faceList );
#ifdef _DEBUG
int *pEmitted = (int*)stackalloc( faceList.Count() * sizeof(int) ); memset( pEmitted, 0, faceList.Count() * sizeof(int) ); #endif
for ( entity_num=1; entity_num < num_entities; ++entity_num ) { if (!IsFuncOccluder(entity_num)) continue;
// Output only those parts of the occluder tree which are a part of the brush
int nOccluder = g_OccluderData.AddToTail(); doccluderdata_t &occluderData = g_OccluderData[ nOccluder ]; occluderData.firstpoly = g_OccluderPolyData.Count(); occluderData.mins.Init( FLT_MAX, FLT_MAX, FLT_MAX ); occluderData.maxs.Init( -FLT_MAX, -FLT_MAX, -FLT_MAX ); occluderData.flags = 0; occluderData.area = -1;
// NOTE: If you change the algorithm by which occluder numbers are allocated,
// then you must also change FixupOnlyEntsOccluderEntities() below
sprintf (str, "%i", nOccluder); SetKeyValue (&entities[entity_num], "occludernumber", str);
int nIndex = g_OccluderInfo.AddToTail(); g_OccluderInfo[nIndex].m_nOccluderEntityIndex = entity_num; sideList.RemoveAll(); GenerateOccluderSideList( entity_num, sideList ); for ( int i = faceList.Count(); --i >= 0; ) { // Skip nodraw surfaces, but not triggers that have been marked as nodraw
face_t *f = faceList[i]; if ( ( texinfo[f->texinfo].flags & SURF_NODRAW ) && (( texinfo[f->texinfo].flags & SURF_TRIGGER ) == 0 ) ) continue;
// Only emit faces that appear in the side list of the occluder
for ( int j = sideList.Count(); --j >= 0; ) { if ( sideList[j] != f->originalface ) continue;
if ( f->numpoints < 3 ) continue;
// not a final face
Assert ( !f->merged && !f->split[0] && !f->split[1] );
#ifdef _DEBUG
Assert( !pEmitted[i] ); pEmitted[i] = entity_num; #endif
int k = g_OccluderPolyData.AddToTail(); doccluderpolydata_t *pOccluderPoly = &g_OccluderPolyData[k];
pOccluderPoly->planenum = f->planenum; pOccluderPoly->vertexcount = f->numpoints; pOccluderPoly->firstvertexindex = g_OccluderVertexIndices.Count(); for( k = 0; k < f->numpoints; ++k ) { g_OccluderVertexIndices.AddToTail( f->vertexnums[k] );
const Vector &p = dvertexes[f->vertexnums[k]].point; VectorMin( occluderData.mins, p, occluderData.mins ); VectorMax( occluderData.maxs, p, occluderData.maxs ); }
break; } }
occluderData.polycount = g_OccluderPolyData.Count() - occluderData.firstpoly;
// Mark this brush as not having brush geometry so it won't be re-emitted with a brush model
entities[entity_num].numbrushes = 0; }
FreeTree( pOccluderTree ); }
//-----------------------------------------------------------------------------
// Set occluder area
//-----------------------------------------------------------------------------
void SetOccluderArea( int nOccluder, int nArea, int nEntityNum ) { if ( g_OccluderData[nOccluder].area <= 0 ) { g_OccluderData[nOccluder].area = nArea; } else if ( (nArea != 0) && (g_OccluderData[nOccluder].area != nArea) ) { const char *pTargetName = ValueForKey( &entities[nEntityNum], "targetname" ); if (!pTargetName) { pTargetName = "<no name>"; } Warning("Occluder \"%s\" straddles multiple areas. This is invalid!\n", pTargetName ); } }
//-----------------------------------------------------------------------------
// Assign occluder areas (must happen *after* the world model is processed)
//-----------------------------------------------------------------------------
void AssignAreaToOccluder( int nOccluder, tree_t *pTree, bool bCrossAreaPortals ) { int nFirstPoly = g_OccluderData[nOccluder].firstpoly; int nEntityNum = g_OccluderInfo[nOccluder].m_nOccluderEntityIndex; for ( int j = 0; j < g_OccluderData[nOccluder].polycount; ++j ) { doccluderpolydata_t *pOccluderPoly = &g_OccluderPolyData[nFirstPoly + j]; int nFirstVertex = pOccluderPoly->firstvertexindex; for ( int k = 0; k < pOccluderPoly->vertexcount; ++k ) { int nVertexIndex = g_OccluderVertexIndices[nFirstVertex + k]; node_t *pNode = NodeForPoint( pTree->headnode, dvertexes[ nVertexIndex ].point );
SetOccluderArea( nOccluder, pNode->area, nEntityNum );
int nOtherSideIndex; portal_t *pPortal; for ( pPortal = pNode->portals; pPortal; pPortal = pPortal->next[!nOtherSideIndex] ) { nOtherSideIndex = (pPortal->nodes[0] == pNode) ? 1 : 0; if (!pPortal->onnode) continue; // edge of world
// Don't cross over area portals for the area check
if ((!bCrossAreaPortals) && pPortal->nodes[nOtherSideIndex]->contents & CONTENTS_AREAPORTAL) continue;
int nAdjacentArea = pPortal->nodes[nOtherSideIndex] ? pPortal->nodes[nOtherSideIndex]->area : 0; SetOccluderArea( nOccluder, nAdjacentArea, nEntityNum ); } } } }
//-----------------------------------------------------------------------------
// Assign occluder areas (must happen *after* the world model is processed)
//-----------------------------------------------------------------------------
void AssignOccluderAreas( tree_t *pTree ) { for ( int i = 0; i < g_OccluderData.Count(); ++i ) { AssignAreaToOccluder( i, pTree, false );
// This can only have happened if the only valid portal out leads into an areaportal
if ( g_OccluderData[i].area <= 0 ) { AssignAreaToOccluder( i, pTree, true ); } } }
//-----------------------------------------------------------------------------
// Make sure the func_occluders have the appropriate data set
//-----------------------------------------------------------------------------
void FixupOnlyEntsOccluderEntities() { char str[64]; int nOccluder = 0; for ( entity_num=1; entity_num < num_entities; ++entity_num ) { if (!IsFuncOccluder(entity_num)) continue;
// NOTE: If you change the algorithm by which occluder numbers are allocated above,
// then you must also change this
sprintf (str, "%i", nOccluder); SetKeyValue (&entities[entity_num], "occludernumber", str); ++nOccluder; } }
void MarkNoDynamicShadowSides() { for ( int iSide=0; iSide < g_MainMap->nummapbrushsides; iSide++ ) { g_MainMap->brushsides[iSide].m_bDynamicShadowsEnabled = true; }
for ( int i=0; i < g_NoDynamicShadowSides.Count(); i++ ) { int brushSideID = g_NoDynamicShadowSides[i]; // Find the side with this ID.
for ( int iSide=0; iSide < g_MainMap->nummapbrushsides; iSide++ ) { if ( g_MainMap->brushsides[iSide].id == brushSideID ) g_MainMap->brushsides[iSide].m_bDynamicShadowsEnabled = false; } } }
//-----------------------------------------------------------------------------
// Compute the 3D skybox areas
//-----------------------------------------------------------------------------
static void Compute3DSkyboxAreas( node_t *headnode, CUtlVector<int>& areas ) { for (int i = 0; i < g_MainMap->num_entities; ++i) { char* pEntity = ValueForKey(&entities[i], "classname"); if (!strcmp(pEntity, "sky_camera")) { // Found a 3D skybox camera, get a leaf that lies in it
node_t *pLeaf = PointInLeaf( headnode, entities[i].origin ); if (pLeaf->contents & CONTENTS_SOLID) { Error ("Error! Entity sky_camera in solid volume! at %.1f %.1f %.1f\n", entities[i].origin.x, entities[i].origin.y, entities[i].origin.z); } areas.AddToTail( pLeaf->area ); } } }
bool Is3DSkyboxArea( int area ) { for ( int i = g_SkyAreas.Count(); --i >=0; ) { if ( g_SkyAreas[i] == area ) return true; } return false; }
/*
============ ProcessModels ============ */ void ProcessModels (void) { BeginBSPFile ();
// Mark sides that have no dynamic shadows.
MarkNoDynamicShadowSides();
// emit the displacement surfaces
EmitInitialDispInfos();
// Clip occluder brushes against each other,
// Remove them from the list of models to process below
EmitOccluderBrushes( );
for ( entity_num=0; entity_num < num_entities; ++entity_num ) { entity_t *pEntity = &entities[entity_num]; if ( !pEntity->numbrushes ) continue;
qprintf ("############### model %i ###############\n", nummodels);
BeginModel ();
if (entity_num == 0) { ProcessWorldModel(); } else { ProcessSubModel( ); }
EndModel ();
if (!verboseentities) { verbose = false; // don't bother printing submodels
} }
// Turn the skybox into a cubemap in case we don't build env_cubemap textures.
Cubemap_CreateDefaultCubemaps(); EndBSPFile (); }
void LoadPhysicsDLL( void ) { PhysicsDLLPath( "vphysics.dll" ); }
void PrintCommandLine( int argc, char **argv ) { Warning( "Command line: " ); for ( int z=0; z < argc; z++ ) { Warning( "\"%s\" ", argv[z] ); } Warning( "\n\n" ); }
int RunVBSP( int argc, char **argv ) { int i; double start, end; char path[1024];
CommandLine()->CreateCmdLine( argc, argv ); MathLib_Init( 2.2f, 2.2f, 0.0f, OVERBRIGHT, false, false, false, false ); InstallSpewFunction(); SpewActivate( "developer", 1 ); CmdLib_InitFileSystem( argv[ argc-1 ] );
Q_StripExtension( ExpandArg( argv[ argc-1 ] ), source, sizeof( source ) ); Q_FileBase( source, mapbase, sizeof( mapbase ) ); strlwr( mapbase );
// Maintaining legacy behavior here to avoid breaking tools: regardless of the extension we are passed, we strip it
// to get the "source" name, and append extensions as desired...
char mapFile[1024]; V_strncpy( mapFile, source, sizeof( mapFile ) ); V_strncat( mapFile, ".bsp", sizeof( mapFile ) );
LoadCmdLineFromFile( argc, argv, mapbase, "vbsp" );
Msg( "Valve Software - vbsp.exe (%s)\n", __DATE__ );
for (i=1 ; i<argc ; i++) { if (!stricmp(argv[i],"-threads")) { numthreads = atoi (argv[i+1]); i++; } else if (!Q_stricmp(argv[i],"-glview")) { glview = true; } else if ( !Q_stricmp(argv[i], "-v") || !Q_stricmp(argv[i], "-verbose") ) { Msg("verbose = true\n"); verbose = true; } else if (!Q_stricmp(argv[i], "-noweld")) { Msg ("noweld = true\n"); noweld = true; } else if (!Q_stricmp(argv[i], "-nocsg")) { Msg ("nocsg = true\n"); nocsg = true; } else if (!Q_stricmp(argv[i], "-noshare")) { Msg ("noshare = true\n"); noshare = true; } else if (!Q_stricmp(argv[i], "-notjunc")) { Msg ("notjunc = true\n"); notjunc = true; } else if (!Q_stricmp(argv[i], "-nowater")) { Msg ("nowater = true\n"); nowater = true; } else if (!Q_stricmp(argv[i], "-noopt")) { Msg ("noopt = true\n"); noopt = true; } else if (!Q_stricmp(argv[i], "-noprune")) { Msg ("noprune = true\n"); noprune = true; } else if (!Q_stricmp(argv[i], "-nomerge")) { Msg ("nomerge = true\n"); nomerge = true; } else if (!Q_stricmp(argv[i], "-nomergewater")) { Msg ("nomergewater = true\n"); nomergewater = true; } else if (!Q_stricmp(argv[i], "-nosubdiv")) { Msg ("nosubdiv = true\n"); nosubdiv = true; } else if (!Q_stricmp(argv[i], "-nodetail")) { Msg ("nodetail = true\n"); nodetail = true; } else if (!Q_stricmp(argv[i], "-fulldetail")) { Msg ("fulldetail = true\n"); fulldetail = true; } else if (!Q_stricmp(argv[i], "-onlyents")) { Msg ("onlyents = true\n"); onlyents = true; } else if (!Q_stricmp(argv[i], "-onlyprops")) { Msg ("onlyprops = true\n"); onlyprops = true; } else if (!Q_stricmp(argv[i], "-micro")) { microvolume = atof(argv[i+1]); Msg ("microvolume = %f\n", microvolume); i++; } else if (!Q_stricmp(argv[i], "-leaktest")) { Msg ("leaktest = true\n"); leaktest = true; } else if (!Q_stricmp(argv[i], "-verboseentities")) { Msg ("verboseentities = true\n"); verboseentities = true; } else if (!Q_stricmp(argv[i], "-snapaxial")) { Msg ("snap axial = true\n"); g_snapAxialPlanes = true; } #if 0
else if (!Q_stricmp(argv[i], "-maxlightmapdim")) { g_maxLightmapDimension = atof(argv[i+1]); Msg ("g_maxLightmapDimension = %f\n", g_maxLightmapDimension); i++; } #endif
else if (!Q_stricmp(argv[i], "-block")) { block_xl = block_xh = atoi(argv[i+1]); block_yl = block_yh = atoi(argv[i+2]); Msg ("block: %i,%i\n", block_xl, block_yl); i+=2; } else if (!Q_stricmp(argv[i], "-blocks")) { block_xl = atoi(argv[i+1]); block_yl = atoi(argv[i+2]); block_xh = atoi(argv[i+3]); block_yh = atoi(argv[i+4]); Msg ("blocks: %i,%i to %i,%i\n", block_xl, block_yl, block_xh, block_yh); i+=4; } else if ( !Q_stricmp( argv[i], "-dumpcollide" ) ) { Msg("Dumping collision models to collideXXX.txt\n" ); dumpcollide = true; } else if ( !Q_stricmp( argv[i], "-dumpstaticprop" ) ) { Msg("Dumping static props to staticpropXXX.txt\n" ); g_DumpStaticProps = true; } else if ( !Q_stricmp( argv[i], "-forceskyvis" ) ) { Msg("Enabled vis in 3d skybox\n" ); g_bSkyVis = true; } else if (!Q_stricmp (argv[i],"-tmpout")) { strcpy (outbase, "/tmp"); } #if 0
else if( !Q_stricmp( argv[i], "-defaultluxelsize" ) ) { g_defaultLuxelSize = atof( argv[i+1] ); i++; } #endif
else if( !Q_stricmp( argv[i], "-luxelscale" ) ) { g_luxelScale = atof( argv[i+1] ); i++; } else if( !strcmp( argv[i], "-minluxelscale" ) ) { g_minLuxelScale = atof( argv[i+1] ); if (g_minLuxelScale < 1) g_minLuxelScale = 1; i++; } else if( !Q_stricmp( argv[i], "-dxlevel" ) ) { g_nDXLevel = atoi( argv[i+1] ); Msg( "DXLevel = %d\n", g_nDXLevel ); i++; } else if( !Q_stricmp( argv[i], "-bumpall" ) ) { g_BumpAll = true; } else if( !Q_stricmp( argv[i], "-low" ) ) { g_bLowPriority = true; } else if( !Q_stricmp( argv[i], "-lightifmissing" ) ) { g_bLightIfMissing = true; } else if ( !Q_stricmp( argv[i], CMDLINEOPTION_NOVCONFIG ) ) { } else if ( !Q_stricmp( argv[i], "-allowdebug" ) || !Q_stricmp( argv[i], "-steam" ) ) { // nothing to do here, but don't bail on this option
} else if ( !Q_stricmp( argv[i], "-vproject" ) || !Q_stricmp( argv[i], "-game" ) || !Q_stricmp( argv[i], "-insert_search_path" ) ) { ++i; } else if ( !Q_stricmp( argv[i], "-keepstalezip" ) ) { g_bKeepStaleZip = true; } else if ( !Q_stricmp( argv[i], "-xbox" ) ) { // enable mandatory xbox extensions
g_NodrawTriggers = true; g_DisableWaterLighting = true; } else if ( !Q_stricmp( argv[i], "-allowdetailcracks")) { g_bAllowDetailCracks = true; } else if ( !Q_stricmp( argv[i], "-novirtualmesh")) { g_bNoVirtualMesh = true; } else if ( !Q_stricmp( argv[i], "-replacematerials" ) ) { g_ReplaceMaterials = true; } else if ( !Q_stricmp(argv[i], "-nodrawtriggers") ) { g_NodrawTriggers = true; } else if ( !Q_stricmp( argv[i], "-FullMinidumps" ) ) { EnableFullMinidumps( true ); } else if ( !Q_stricmp( argv[i], "-embed" ) && i < argc - 1 ) { V_MakeAbsolutePath( g_szEmbedDir, sizeof( g_szEmbedDir ), argv[++i], "." ); V_FixSlashes( g_szEmbedDir ); if ( !V_RemoveDotSlashes( g_szEmbedDir ) ) { Error( "Bad -embed - Can't resolve pathname for '%s'", g_szEmbedDir ); break; } V_StripTrailingSlash( g_szEmbedDir ); g_pFullFileSystem->AddSearchPath( g_szEmbedDir, "GAME", PATH_ADD_TO_TAIL ); g_pFullFileSystem->AddSearchPath( g_szEmbedDir, "MOD", PATH_ADD_TO_TAIL ); } else if (argv[i][0] == '-') { Warning("VBSP: Unknown option \"%s\"\n\n", argv[i]); i = 100000; // force it to print the usage
break; } else break; }
if (i != argc - 1) { PrintCommandLine( argc, argv );
Warning( "usage : vbsp [options...] mapfile\n" "example: vbsp -onlyents c:\\hl2\\hl2\\maps\\test\n" "\n" "Common options (use -v to see all options):\n" "\n" " -v (or -verbose): Turn on verbose output (also shows more command\n" " line options).\n" "\n" " -onlyents : This option causes vbsp only import the entities from the .vmf\n" " file. -onlyents won't reimport brush models.\n" " -onlyprops : Only update the static props and detail props.\n" " -glview : Writes .gl files in the current directory that can be viewed\n" " with glview.exe. If you use -tmpout, it will write the files\n" " into the \\tmp folder.\n" " -nodetail : Get rid of all detail geometry. The geometry left over is\n" " what affects visibility.\n" " -nowater : Get rid of water brushes.\n" " -low : Run as an idle-priority process.\n" " -embed <directory> : Use <directory> as an additional search path for assets\n" " and embed all assets in this directory into the compiled\n" " map\n" "\n" " -vproject <directory> : Override the VPROJECT environment variable.\n" " -game <directory> : Same as -vproject.\n" "\n" );
if ( verbose ) { Warning( "Other options :\n" " -novconfig : Don't bring up graphical UI on vproject errors.\n" " -threads : Control the number of threads vbsp uses (defaults to the # of\n" " processors on your machine).\n" " -verboseentities: If -v is on, this disables verbose output for submodels.\n" " -noweld : Don't join face vertices together.\n" " -nocsg : Don't chop out intersecting brush areas.\n" " -noshare : Emit unique face edges instead of sharing them.\n" " -notjunc : Don't fixup t-junctions.\n" " -noopt : By default, vbsp removes the 'outer shell' of the map, which\n" " are all the faces you can't see because you can never get\n" " outside the map. -noopt disables this behaviour.\n" " -noprune : Don't prune neighboring solid nodes.\n" " -nomerge : Don't merge together chopped faces on nodes.\n" " -nomergewater: Don't merge together chopped faces on water.\n" " -nosubdiv : Don't subdivide faces for lightmapping.\n" " -micro <#> : vbsp will warn when brushes are output with a volume less\n" " than this number (default: 1.0).\n" " -fulldetail : Mark all detail geometry as normal geometry (so all detail\n" " geometry will affect visibility).\n" " -leaktest : Stop processing the map if a leak is detected. Whether or not\n" " this flag is set, a leak file will be written out at\n" " <vmf filename>.lin, and it can be imported into Hammer.\n" " -bumpall : Force all surfaces to be bump mapped.\n" " -snapaxial : Snap axial planes to integer coordinates.\n" " -block # # : Control the grid size mins that vbsp chops the level on.\n" " -blocks # # # # : Enter the mins and maxs for the grid size vbsp uses.\n" " -dumpstaticprops: Dump static props to staticprop*.txt\n" " -dumpcollide : Write files with collision info.\n" " -forceskyvis : Enable vis calculations in 3d skybox leaves\n" " -luxelscale # : Scale all lightmaps by this amount (default: 1.0).\n" " -minluxelscale #: No luxel scale will be lower than this amount (default: 1.0).\n" " -lightifmissing : Force lightmaps to be generated for all surfaces even if\n" " they don't need lightmaps.\n" " -keepstalezip : Keep the BSP's zip files intact but regenerate everything\n" " else.\n" " -virtualdispphysics : Use virtual (not precomputed) displacement collision models\n" " -xbox : Enable mandatory xbox options\n" " -x360 : Generate Xbox360 version of vsp\n" " -nox360 : Disable generation Xbox360 version of vsp (default)\n" " -replacematerials : Substitute materials according to materialsub.txt in content\\maps\n" " -FullMinidumps : Write large minidumps on crash.\n" ); }
DeleteCmdLine( argc, argv ); CmdLib_Cleanup(); CmdLib_Exit( 1 ); }
// Sanity check
if ( *g_szEmbedDir && ( onlyents || onlyprops ) ) { Warning( "-embed only makes sense alongside full BSP compiles.\n" "\n" "Use the bspzip utility to update embedded files.\n" ); DeleteCmdLine( argc, argv ); CmdLib_Cleanup(); CmdLib_Exit( 1 ); }
start = Plat_FloatTime();
// Run in the background?
if( g_bLowPriority ) { SetLowPriority(); }
if( ( g_nDXLevel != 0 ) && ( g_nDXLevel < 80 ) ) { g_BumpAll = false; }
if( g_luxelScale == 1.0f ) { if ( g_nDXLevel == 70 ) { g_luxelScale = 4.0f; } }
ThreadSetDefault (); numthreads = 1; // multiple threads aren't helping...
// Setup the logfile.
char logFile[512]; _snprintf( logFile, sizeof(logFile), "%s.log", source ); SetSpewFunctionLogFile( logFile );
LoadPhysicsDLL(); LoadSurfaceProperties();
#if 0
Msg( "qdir: %s This is the the path of the initial source file \n", qdir ); Msg( "gamedir: %s This is the base engine + mod-specific game dir (e.g. d:/tf2/mytfmod/) \n", gamedir ); Msg( "basegamedir: %s This is the base engine + base game directory (e.g. e:/hl2/hl2/, or d:/tf2/tf2/ )\n", basegamedir ); #endif
sprintf( materialPath, "%smaterials", gamedir ); InitMaterialSystem( materialPath, CmdLib_GetFileSystemFactory() ); Msg( "materialPath: %s\n", materialPath );
// delete portal and line files
sprintf (path, "%s.prt", source); remove (path); sprintf (path, "%s.lin", source); remove (path);
strcpy (name, ExpandArg (argv[i]));
const char *pszExtension = V_GetFileExtension( name ); if ( !pszExtension ) { V_SetExtension( name, ".vmm", sizeof( name ) ); if ( !FileExists( name ) ) { V_SetExtension( name, ".vmf", sizeof( name ) ); } }
// if we're combining materials, load the script file
if ( g_ReplaceMaterials ) { LoadMaterialReplacementKeys( gamedir, mapbase ); }
//
// if onlyents, just grab the entites and resave
//
if (onlyents) { LoadBSPFile (mapFile); num_entities = 0; // Clear out the cubemap samples since they will be reparsed even with -onlyents
g_nCubemapSamples = 0;
// Mark as stale since the lighting could be screwed with new ents.
AddBufferToPak( GetPakFile(), "stale.txt", "stale", strlen( "stale" ) + 1, false );
LoadMapFile (name); SetModelNumbers (); SetLightStyles ();
// NOTE: If we ever precompute lighting for static props in
// vrad, EmitStaticProps should be removed here
// Emit static props found in the .vmf file
EmitStaticProps();
// NOTE: Don't deal with detail props here, it blows away lighting
// Recompute the skybox
ComputeBoundsNoSkybox();
// Make sure that we have a water lod control eneity if we have water in the map.
EnsurePresenceOfWaterLODControlEntity();
// Make sure the func_occluders have the appropriate data set
FixupOnlyEntsOccluderEntities();
// Doing this here because stuff abov may filter out entities
UnparseEntities ();
WriteBSPFile (mapFile); } else if (onlyprops) { // In the only props case, deal with static + detail props only
LoadBSPFile (mapFile);
LoadMapFile(name); SetModelNumbers(); SetLightStyles();
// Emit static props found in the .vmf file
EmitStaticProps();
// Place detail props found in .vmf and based on material properties
LoadEmitDetailObjectDictionary( gamedir ); EmitDetailObjects();
WriteBSPFile (mapFile); } else { //
// start from scratch
//
// Load just the file system from the bsp
if( g_bKeepStaleZip && FileExists( mapFile ) ) { LoadBSPFile_FileSystemOnly (mapFile); // Mark as stale since the lighting could be screwed with new ents.
AddBufferToPak( GetPakFile(), "stale.txt", "stale", strlen( "stale" ) + 1, false ); }
LoadMapFile (name); WorldVertexTransitionFixup(); if( ( g_nDXLevel == 0 ) || ( g_nDXLevel >= 70 ) ) { Cubemap_FixupBrushSidesMaterials(); Cubemap_AttachDefaultCubemapToSpecularSides(); Cubemap_AddUnreferencedCubemaps(); } SetModelNumbers (); SetLightStyles (); LoadEmitDetailObjectDictionary( gamedir ); ProcessModels ();
// Add embed dir if provided
if ( *g_szEmbedDir ) { AddDirToPak( GetPakFile(), g_szEmbedDir ); WriteBSPFile( mapFile ); } }
end = Plat_FloatTime(); char str[512]; GetHourMinuteSecondsString( (int)( end - start ), str, sizeof( str ) ); Msg( "%s elapsed\n", str );
DeleteCmdLine( argc, argv ); ReleasePakFileLumps(); DeleteMaterialReplacementKeys(); ShutdownMaterialSystem(); CmdLib_Cleanup(); return 0; }
/*
============= main ============ */ int main (int argc, char **argv) { // Install an exception handler.
SetupDefaultToolsMinidumpHandler(); return RunVBSP( argc, argv ); }
|