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.
1443 lines
38 KiB
1443 lines
38 KiB
//========= 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 ); |
|
} |
|
|
|
|
|
|