forked from FWGS/Paranoia2
634 lines
13 KiB
C++
634 lines
13 KiB
C++
/***
|
|
*
|
|
* Copyright (c) 1996-2002, Valve LLC. All rights reserved.
|
|
*
|
|
* This product contains software technology licensed from Id
|
|
* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc.
|
|
* All Rights Reserved.
|
|
*
|
|
****/
|
|
|
|
// qbsp.c
|
|
|
|
#include "bsp5.h"
|
|
|
|
//
|
|
// command line flags
|
|
//
|
|
bool g_nofill = DEFAULT_NOFILL;
|
|
bool g_notjunc = DEFAULT_NOTJUNC;
|
|
bool g_noclip = DEFAULT_NOCLIP;
|
|
bool g_forcevis = DEFAULT_FORCEVIS;
|
|
|
|
int g_maxnode_size = DEFAULT_MAXNODE_SIZE;
|
|
int g_merge_level = DEFAULT_MERGE_LEVEL;
|
|
vec_t g_prtepsilon = PRTCHOP_EPSILON;
|
|
|
|
char g_pointfilename[1024];
|
|
char g_linefilename[1024];
|
|
char g_portfilename[1024];
|
|
|
|
//===========================================================================
|
|
/*
|
|
============
|
|
ReadMapPlanes
|
|
============
|
|
*/
|
|
void ReadMapPlanes( const char *source )
|
|
{
|
|
char path[1024];
|
|
size_t filelen;
|
|
FILE *f;
|
|
|
|
Q_snprintf( path, sizeof( path ), "%s.pln", source );
|
|
f = fopen( path, "rb" );
|
|
if( !f ) COM_FatalError( "couldn't open %s\n", path );
|
|
|
|
fseek( f, 0, SEEK_END );
|
|
filelen = ftell( f );
|
|
fseek( f, 0, SEEK_SET );
|
|
|
|
if( filelen > sizeof( g_mapplanes ))
|
|
COM_FatalError( "insufficient MAX_INTERNAL_MAP_PLANES size\n" );
|
|
|
|
if( filelen % sizeof( plane_t ))
|
|
COM_FatalError( "msimatch plane_t struct between csg and bsp\n" );
|
|
|
|
if( fread( g_mapplanes, 1, filelen, f ) != filelen )
|
|
COM_FatalError( "failed to read mapplanes\n" );
|
|
|
|
g_nummapplanes = filelen / sizeof( plane_t );
|
|
|
|
fclose( f );
|
|
|
|
// clear out hash_chain, now is output planenum!
|
|
for( int i = 0; i < g_nummapplanes; i++ )
|
|
g_mapplanes[i].outplanenum = -1;
|
|
}
|
|
|
|
/*
|
|
============
|
|
ReadHullSizes
|
|
============
|
|
*/
|
|
void ReadHullSizes( const char *source )
|
|
{
|
|
float x1, y1, z1;
|
|
float x2, y2, z2;
|
|
char path[1024];
|
|
FILE *f;
|
|
|
|
Q_snprintf( path, sizeof( path ), "%s.hsz", source );
|
|
f = fopen( path, "rb" );
|
|
if( !f ) return; // just use predefined sizes
|
|
|
|
for( int i = 0; i < MAX_MAP_HULLS; i++ )
|
|
{
|
|
int count = fscanf( f, "%f %f %f %f %f %f\n", &x1, &y1, &z1, &x2, &y2, &z2 );
|
|
if( count != 6 ) COM_FatalError( "Load hull size (line %i): scanf failure", i + 1 );
|
|
|
|
g_hull_size[i][0][0] = x1;
|
|
g_hull_size[i][0][1] = y1;
|
|
g_hull_size[i][0][2] = z1;
|
|
g_hull_size[i][1][0] = x2;
|
|
g_hull_size[i][1][1] = y2;
|
|
g_hull_size[i][1][2] = z2;
|
|
}
|
|
|
|
fclose( f );
|
|
}
|
|
|
|
//===========================================================================
|
|
/*
|
|
==================
|
|
NewFaceFromFace
|
|
|
|
Duplicates the non point information of a face, used by SplitFace and
|
|
MergeFace.
|
|
==================
|
|
*/
|
|
face_t *NewFaceFromFace( face_t *in )
|
|
{
|
|
face_t *newf;
|
|
|
|
newf = AllocFace ();
|
|
|
|
newf->planenum = in->planenum;
|
|
newf->texturenum = in->texturenum;
|
|
newf->original = in->original;
|
|
newf->contents = in->contents;
|
|
newf->detaillevel = in->detaillevel;
|
|
newf->facestyle = in->facestyle;
|
|
|
|
return newf;
|
|
}
|
|
|
|
/*
|
|
==================
|
|
SplitFace
|
|
|
|
==================
|
|
*/
|
|
void SplitFaceEpsilon( face_t *in, plane_t *split, face_t **front, face_t **back, vec_t epsilon, bool keepsource )
|
|
{
|
|
vec_t *facenormal = NULL;
|
|
winding_t *frontw, *backw;
|
|
|
|
*front = *back = NULL;
|
|
|
|
if( in->detaillevel )
|
|
facenormal = g_mapplanes[in->planenum].normal;
|
|
|
|
DivideWindingEpsilon( in->w, split->normal, split->dist, epsilon, &frontw, &backw, facenormal );
|
|
|
|
if( frontw && backw )
|
|
{
|
|
// face is divided
|
|
*front = NewFaceFromFace( in );
|
|
*back = NewFaceFromFace( in );
|
|
(*front)->w = frontw;
|
|
(*back)->w = backw;
|
|
|
|
if( !keepsource )
|
|
{
|
|
// free original
|
|
FreeFace( in );
|
|
}
|
|
}
|
|
else if( frontw != NULL )
|
|
{
|
|
*front = in;
|
|
}
|
|
else if( backw != NULL )
|
|
{
|
|
*back = in;
|
|
}
|
|
}
|
|
|
|
void SplitFace( face_t *in, plane_t *split, face_t **front, face_t **back, bool keepsource )
|
|
{
|
|
SplitFaceEpsilon( in, split, front, back, ON_EPSILON, keepsource );
|
|
}
|
|
|
|
//===========================================================================
|
|
|
|
int c_activefaces, c_peakfaces;
|
|
int c_activesurfaces, c_peaksurfaces;
|
|
int c_activeportals, c_peakportals;
|
|
|
|
void PrintMemory( void )
|
|
{
|
|
Msg( "faces : %6i (%6i)\n", c_activefaces, c_peakfaces );
|
|
Msg( "surfaces: %6i (%6i)\n", c_activesurfaces, c_peaksurfaces );
|
|
Msg( "portals : %6i (%6i)\n", c_activeportals, c_peakportals );
|
|
}
|
|
|
|
/*
|
|
===========
|
|
AllocFace
|
|
===========
|
|
*/
|
|
face_t *AllocFace( void )
|
|
{
|
|
face_t *f;
|
|
|
|
c_activefaces++;
|
|
|
|
if( c_activefaces > c_peakfaces )
|
|
c_peakfaces = c_activefaces;
|
|
|
|
f = (face_t *)Mem_Alloc( sizeof( face_t ), C_SURFACE );
|
|
f->planenum = -1;
|
|
|
|
return f;
|
|
}
|
|
|
|
/*
|
|
===========
|
|
AddFaceToBounds
|
|
===========
|
|
*/
|
|
void AddFaceToBounds( face_t *f, vec3_t mins, vec3_t maxs )
|
|
{
|
|
winding_t *w = f->w;
|
|
|
|
for ( int i = 0; i < w->numpoints; i++ )
|
|
AddPointToBounds( w->p[i], mins, maxs );
|
|
}
|
|
|
|
/*
|
|
===========
|
|
FreeFace
|
|
===========
|
|
*/
|
|
void FreeFace( face_t *f )
|
|
{
|
|
if( !f ) return;
|
|
|
|
if( f->w ) FreeWinding( f->w );
|
|
Mem_Free( f, C_SURFACE );
|
|
|
|
c_activefaces--;
|
|
}
|
|
|
|
/*
|
|
==================
|
|
UnlinkFace
|
|
|
|
release specified face or purge all chain
|
|
==================
|
|
*/
|
|
void UnlinkFace( face_t **head, face_t *face )
|
|
{
|
|
face_t **prev = head;
|
|
face_t *cur;
|
|
|
|
while( 1 )
|
|
{
|
|
cur = *prev;
|
|
if( !cur ) break;
|
|
|
|
if( face != NULL && face != cur )
|
|
{
|
|
prev = &cur->next;
|
|
continue;
|
|
}
|
|
|
|
*prev = cur->next;
|
|
}
|
|
}
|
|
|
|
/*
|
|
==================
|
|
CountListFaces
|
|
|
|
return count of valid faces
|
|
==================
|
|
*/
|
|
int CountListFaces( face_t *list )
|
|
{
|
|
int count = 0;
|
|
|
|
for( face_t *f1 = list; f1 != NULL; f1 = f1->next )
|
|
if( f1->w ) count++;
|
|
|
|
return count;
|
|
}
|
|
|
|
/*
|
|
===========
|
|
AllocSurface
|
|
===========
|
|
*/
|
|
surface_t *AllocSurface( void )
|
|
{
|
|
surface_t *s;
|
|
|
|
c_activesurfaces++;
|
|
|
|
if( c_activesurfaces > c_peaksurfaces )
|
|
c_peaksurfaces = c_activesurfaces;
|
|
|
|
s = (surface_t *)Mem_Alloc( sizeof( surface_t ), C_SURFACE );
|
|
ClearBounds( s->mins, s->maxs );
|
|
s->planenum = -1;
|
|
|
|
return s;
|
|
}
|
|
|
|
/*
|
|
===========
|
|
FreeSurface
|
|
===========
|
|
*/
|
|
void FreeSurface( surface_t *s )
|
|
{
|
|
Mem_Free( s, C_SURFACE );
|
|
c_activesurfaces--;
|
|
}
|
|
|
|
/*
|
|
===========
|
|
AllocPortal
|
|
===========
|
|
*/
|
|
portal_t *AllocPortal( void )
|
|
{
|
|
c_activeportals++;
|
|
if( c_activeportals > c_peakportals )
|
|
c_peakportals = c_activeportals;
|
|
|
|
return (portal_t *)Mem_Alloc( sizeof( portal_t ), C_PORTAL );
|
|
}
|
|
|
|
/*
|
|
===========
|
|
FreePortal
|
|
===========
|
|
*/
|
|
void FreePortal( portal_t *p )
|
|
{
|
|
c_activeportals--;
|
|
Mem_Free( p, C_PORTAL );
|
|
}
|
|
|
|
/*
|
|
===========
|
|
AllocNode
|
|
===========
|
|
*/
|
|
node_t *AllocNode( void )
|
|
{
|
|
return (node_t *)Mem_Alloc( sizeof( node_t ), C_LEAFNODE );
|
|
}
|
|
|
|
/*
|
|
===========
|
|
FreeNode
|
|
===========
|
|
*/
|
|
void FreeNode( node_t *n )
|
|
{
|
|
Mem_Free( n, C_LEAFNODE );
|
|
}
|
|
|
|
/*
|
|
===========
|
|
FreeLeaf
|
|
===========
|
|
*/
|
|
void FreeLeaf( node_t *n )
|
|
{
|
|
if( n->markfaces )
|
|
Mem_Free( n->markfaces );
|
|
Mem_Free( n, C_LEAFNODE );
|
|
}
|
|
|
|
//===========================================================================
|
|
/*
|
|
=================
|
|
CreateSingleHull
|
|
=================
|
|
*/
|
|
void CreateSingleHull( const char *source, int hullnum )
|
|
{
|
|
int modnum = 0;
|
|
FILE *brushfile;
|
|
FILE *polyfile;
|
|
char name[1024];
|
|
tree_t *tree;
|
|
|
|
Msg( "CreateHull: %i\n", hullnum );
|
|
|
|
Q_snprintf( name, sizeof( name ), "%s.p%i", source, hullnum );
|
|
polyfile = fopen( name, "r" );
|
|
if( !polyfile ) COM_FatalError( "Can't open %s", name );
|
|
|
|
Q_snprintf( name, sizeof( name ), "%s.b%i", source, hullnum );
|
|
brushfile = fopen( name, "r" );
|
|
if( !brushfile ) COM_FatalError( "Can't open %s", name );
|
|
|
|
while(( tree = MakeTreeFromHullFaces( polyfile, brushfile )) != NULL )
|
|
{
|
|
tree = TreeProcessModel( tree, modnum, hullnum );
|
|
|
|
if( hullnum == 0 ) EmitDrawNodes( tree );
|
|
else EmitClipNodes( tree, modnum, hullnum );
|
|
|
|
FreeTree( tree );
|
|
modnum++;
|
|
}
|
|
|
|
Q_snprintf( name, sizeof( name ), "%s.p%i", source, hullnum );
|
|
fclose( polyfile );
|
|
unlink( name );
|
|
|
|
Q_snprintf( name, sizeof( name ), "%s.b%i", source, hullnum );
|
|
fclose( brushfile );
|
|
unlink( name );
|
|
}
|
|
|
|
/*
|
|
=================
|
|
ProcessFile
|
|
|
|
=================
|
|
*/
|
|
void ProcessFile( const char *source )
|
|
{
|
|
char bspfilename[1024];
|
|
char name[1024];
|
|
int i;
|
|
|
|
// create filenames
|
|
Q_snprintf( g_portfilename, sizeof( g_portfilename ), "%s.prt", source );
|
|
remove( g_portfilename );
|
|
|
|
Q_snprintf( g_pointfilename, sizeof( g_pointfilename ), "%s.pts", source );
|
|
remove( g_pointfilename );
|
|
|
|
Q_snprintf( g_linefilename, sizeof( g_linefilename ), "%s.lin", source );
|
|
remove( g_linefilename );
|
|
|
|
// reading hull sizes from text-file
|
|
ReadHullSizes( source );
|
|
|
|
// reading planes from binary dump
|
|
ReadMapPlanes( source );
|
|
|
|
// load the output of qcsg
|
|
Q_snprintf( bspfilename, sizeof( bspfilename ), "%s.bsp", source );
|
|
LoadBSPFile( bspfilename );
|
|
|
|
ParseEntities ();
|
|
|
|
// init the tables to be shared by all models
|
|
BeginBSPFile ();
|
|
|
|
for( i = 0; i < MAX_MAP_HULLS; i++ )
|
|
{
|
|
CreateSingleHull( source, i );
|
|
}
|
|
|
|
// write the updated bsp file out
|
|
FinishBSPFile( bspfilename );
|
|
|
|
Q_snprintf( name, sizeof( name ), "%s.pln", source );
|
|
unlink( name );
|
|
|
|
Q_snprintf( name, sizeof( name ), "%s.hsz", source );
|
|
unlink( name );
|
|
}
|
|
|
|
//=========================================
|
|
/*
|
|
============
|
|
GetNodeSize
|
|
|
|
print node size
|
|
============
|
|
*/
|
|
const char *GetNodeSize( int nodesize )
|
|
{
|
|
if( nodesize == DEFAULT_MAXNODE_SIZE )
|
|
return va( "%s", "Auto" );
|
|
return va( "%d", nodesize );
|
|
}
|
|
|
|
/*
|
|
============
|
|
PrintBspSettings
|
|
|
|
show compiler settings like ZHLT
|
|
============
|
|
*/
|
|
static void PrintBspSettings( void )
|
|
{
|
|
Msg( "\nCurrent p2bsp settings\n" );
|
|
Msg( "Name | Setting | Default\n" );
|
|
Msg( "---------------------|-----------|-------------------------\n" );
|
|
Msg( "developer [ %7d ] [ %7d ]\n", GetDeveloperLevel(), DEFAULT_DEVELOPER );
|
|
Msg( "max node size [ %7s ] [ %7s ]\n", GetNodeSize( g_maxnode_size ), GetNodeSize( DEFAULT_MAXNODE_SIZE ));
|
|
Msg( "merge faces depth [ %7d ] [ %7d ]\n", g_merge_level, DEFAULT_MERGE_LEVEL );
|
|
Msg( "notjunc [ %7s ] [ %7s ]\n", g_notjunc ? "on" : "off", DEFAULT_NOTJUNC ? "on" : "off" );
|
|
Msg( "noclip [ %7s ] [ %7s ]\n", g_noclip ? "on" : "off", DEFAULT_NOCLIP ? "on" : "off" );
|
|
Msg( "nofill [ %7s ] [ %7s ]\n", g_nofill ? "on" : "off", DEFAULT_NOFILL ? "on" : "off" );
|
|
Msg( "portal chop epsilon [ %.6f] [ %.6f]\n", g_prtepsilon, PRTCHOP_EPSILON );
|
|
Msg( "force vis [ %7s ] [ %7s ]\n", g_forcevis ? "on" : "off", DEFAULT_FORCEVIS ? "on" : "off" );
|
|
Msg( "\n" );
|
|
}
|
|
|
|
/*
|
|
============
|
|
PrintBspUsage
|
|
|
|
show compiler usage like ZHLT
|
|
============
|
|
*/
|
|
static void PrintBspUsage( void )
|
|
{
|
|
Msg( "\n-= p2bsp Options =-\n\n" );
|
|
Msg( " -dev # : compile with developer message (1 - 4). default is %d\n", DEFAULT_DEVELOPER );
|
|
Msg( " -threads # : manually specify the number of threads to run\n" );
|
|
Msg( " -noclip : don't create clipping hulls\n" );
|
|
Msg( " -notjunc : don't break edges on t-junctions (not for final runs)\n" );
|
|
Msg( " -nofill : don't fill outside (used for brush models, not levels)\n" );
|
|
Msg( " -noforcevis : don't make a .prt if the map leaks\n" );
|
|
Msg( " -merge : merge together chopped faces on nodes (merging depth level)\n" );
|
|
Msg( " -maxnodesize val : sets the maximum portal node size\n" );
|
|
Msg( " -epsilon : portal chop precision epsilon\n" );
|
|
Msg( " mapfile : the mapfile to compile\n\n" );
|
|
|
|
exit( 1 );
|
|
}
|
|
|
|
/*
|
|
==================
|
|
main
|
|
|
|
==================
|
|
*/
|
|
int main( int argc, char **argv )
|
|
{
|
|
int i;
|
|
double start, end;
|
|
char source[1024];
|
|
char str[64];
|
|
|
|
atexit( Sys_CloseLog );
|
|
source[0] = '\0';
|
|
|
|
for( i = 1; i < argc; i++ )
|
|
{
|
|
if( !Q_strcmp( argv[i], "-dev" ))
|
|
{
|
|
SetDeveloperLevel( atoi( argv[i+1] ));
|
|
i++;
|
|
}
|
|
else if( !Q_strcmp( argv[i], "-threads" ))
|
|
{
|
|
g_numthreads = atoi( argv[i+1] );
|
|
i++;
|
|
}
|
|
else if( !Q_strcmp( argv[i], "-noclip" ))
|
|
{
|
|
g_noclip = true;
|
|
}
|
|
else if( !Q_strcmp( argv[i], "-notjunc" ))
|
|
{
|
|
g_notjunc = true;
|
|
}
|
|
else if( !Q_strcmp( argv[i], "-nofill" ))
|
|
{
|
|
g_nofill = true;
|
|
}
|
|
else if( !Q_strcmp( argv[i], "-noforcevis" ))
|
|
{
|
|
g_forcevis = false;
|
|
}
|
|
else if( !Q_strcmp( argv[i], "-merge" ))
|
|
{
|
|
g_merge_level = atoi( argv[i+1] );
|
|
g_merge_level = bound( 0, g_merge_level, 4 );
|
|
i++;
|
|
}
|
|
else if( !Q_strcmp( argv[i], "-maxnodesize" ))
|
|
{
|
|
g_maxnode_size = atoi( argv[i+1] );
|
|
g_maxnode_size = bound( 256, g_maxnode_size, 65536 );
|
|
i++;
|
|
}
|
|
else if( !Q_strcmp( argv[i], "-epsilon" ))
|
|
{
|
|
g_prtepsilon = atof( argv[i+1] );
|
|
i++;
|
|
}
|
|
else if( argv[i][0] == '-' )
|
|
{
|
|
MsgDev( D_ERROR, "\nUnknown option \"%s\"\n", argv[i] );
|
|
break;
|
|
}
|
|
else if( !source[0] )
|
|
{
|
|
Q_strncpy( source, COM_ExpandArg( argv[i] ), sizeof( source ));
|
|
COM_StripExtension( source );
|
|
}
|
|
else
|
|
{
|
|
MsgDev( D_ERROR, "\nUnknown option \"%s\"\n", argv[i] );
|
|
break;
|
|
}
|
|
}
|
|
|
|
if( i != argc || !source[0] )
|
|
{
|
|
if( !source[0] )
|
|
Msg( "no mapfile specified\n" );
|
|
PrintBspUsage();
|
|
}
|
|
|
|
start = I_FloatTime ();
|
|
|
|
Sys_InitLogAppend( va( "%s.log", source ));
|
|
|
|
Msg( "\n%s %s (%s)\n", TOOLNAME, VERSIONSTRING, __DATE__ );
|
|
|
|
PrintBspSettings();
|
|
|
|
ThreadSetDefault ();
|
|
|
|
ProcessFile( source );
|
|
|
|
FreeEntities();
|
|
|
|
// now check for leaks
|
|
SetDeveloperLevel( D_REPORT );
|
|
Mem_Check();
|
|
|
|
end = I_FloatTime ();
|
|
Q_timestring((int)( end - start ), str );
|
|
Msg( "%s elapsed\n", str );
|
|
|
|
return 0;
|
|
}
|