mirror of
https://github.com/UberGames/GtkRadiant.git
synced 2024-11-29 07:02:26 +00:00
1030 lines
23 KiB
C
1030 lines
23 KiB
C
/*
|
|
Copyright (C) 1999-2007 id Software, Inc. and contributors.
|
|
For a list of contributors, see the accompanying CONTRIBUTORS file.
|
|
|
|
This file is part of GtkRadiant.
|
|
|
|
GtkRadiant is free software; you can redistribute it and/or modify
|
|
it under the terms of the GNU General Public License as published by
|
|
the Free Software Foundation; either version 2 of the License, or
|
|
(at your option) any later version.
|
|
|
|
GtkRadiant is distributed in the hope that it will be useful,
|
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
GNU General Public License for more details.
|
|
|
|
You should have received a copy of the GNU General Public License
|
|
along with GtkRadiant; if not, write to the Free Software
|
|
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
|
*/
|
|
// map.c
|
|
|
|
#include "qbsp.h"
|
|
|
|
extern qboolean onlyents;
|
|
|
|
int nummapbrushes;
|
|
mapbrush_t mapbrushes[MAX_MAP_BRUSHES];
|
|
|
|
int nummapbrushsides;
|
|
side_t brushsides[MAX_MAP_SIDES];
|
|
brush_texture_t side_brushtextures[MAX_MAP_SIDES];
|
|
|
|
int nummapplanes;
|
|
plane_t mapplanes[MAX_MAP_PLANES];
|
|
|
|
#define PLANE_HASHES 1024
|
|
plane_t *planehash[PLANE_HASHES];
|
|
|
|
vec3_t map_mins, map_maxs;
|
|
|
|
// undefine to make plane finding use linear sort
|
|
#define USE_HASHING
|
|
|
|
void TestExpandBrushes( void );
|
|
|
|
int c_boxbevels;
|
|
int c_edgebevels;
|
|
|
|
int c_areaportals;
|
|
|
|
int c_clipbrushes;
|
|
|
|
/*
|
|
=============================================================================
|
|
|
|
PLANE FINDING
|
|
|
|
=============================================================================
|
|
*/
|
|
|
|
|
|
/*
|
|
=================
|
|
PlaneTypeForNormal
|
|
=================
|
|
*/
|
|
int PlaneTypeForNormal( vec3_t normal ){
|
|
vec_t ax, ay, az;
|
|
|
|
// NOTE: should these have an epsilon around 1.0?
|
|
if ( normal[0] == 1.0 || normal[0] == -1.0 ) {
|
|
return PLANE_X;
|
|
}
|
|
if ( normal[1] == 1.0 || normal[1] == -1.0 ) {
|
|
return PLANE_Y;
|
|
}
|
|
if ( normal[2] == 1.0 || normal[2] == -1.0 ) {
|
|
return PLANE_Z;
|
|
}
|
|
|
|
ax = fabs( normal[0] );
|
|
ay = fabs( normal[1] );
|
|
az = fabs( normal[2] );
|
|
|
|
if ( ax >= ay && ax >= az ) {
|
|
return PLANE_ANYX;
|
|
}
|
|
if ( ay >= ax && ay >= az ) {
|
|
return PLANE_ANYY;
|
|
}
|
|
return PLANE_ANYZ;
|
|
}
|
|
|
|
/*
|
|
================
|
|
PlaneEqual
|
|
================
|
|
*/
|
|
#define NORMAL_EPSILON 0.00001
|
|
#define DIST_EPSILON 0.01
|
|
qboolean PlaneEqual( plane_t *p, vec3_t normal, vec_t dist ){
|
|
#if 1
|
|
if (
|
|
fabs( p->normal[0] - normal[0] ) < NORMAL_EPSILON
|
|
&& fabs( p->normal[1] - normal[1] ) < NORMAL_EPSILON
|
|
&& fabs( p->normal[2] - normal[2] ) < NORMAL_EPSILON
|
|
&& fabs( p->dist - dist ) < DIST_EPSILON ) {
|
|
return true;
|
|
}
|
|
#else
|
|
if ( p->normal[0] == normal[0]
|
|
&& p->normal[1] == normal[1]
|
|
&& p->normal[2] == normal[2]
|
|
&& p->dist == dist ) {
|
|
return true;
|
|
}
|
|
#endif
|
|
return false;
|
|
}
|
|
|
|
/*
|
|
================
|
|
AddPlaneToHash
|
|
================
|
|
*/
|
|
void AddPlaneToHash( plane_t *p ){
|
|
int hash;
|
|
|
|
hash = (int)fabs( p->dist ) / 8;
|
|
hash &= ( PLANE_HASHES - 1 );
|
|
|
|
p->hash_chain = planehash[hash];
|
|
planehash[hash] = p;
|
|
}
|
|
|
|
/*
|
|
================
|
|
CreateNewFloatPlane
|
|
================
|
|
*/
|
|
int CreateNewFloatPlane( vec3_t normal, vec_t dist ){
|
|
plane_t *p, temp;
|
|
|
|
if ( VectorLength( normal ) < 0.5 ) {
|
|
Error( "FloatPlane: bad normal" );
|
|
}
|
|
// create a new plane
|
|
if ( nummapplanes + 2 > MAX_MAP_PLANES ) {
|
|
Error( "MAX_MAP_PLANES" );
|
|
}
|
|
|
|
p = &mapplanes[nummapplanes];
|
|
VectorCopy( normal, p->normal );
|
|
p->dist = dist;
|
|
p->type = ( p + 1 )->type = PlaneTypeForNormal( p->normal );
|
|
|
|
VectorSubtract( vec3_origin, normal, ( p + 1 )->normal );
|
|
( p + 1 )->dist = -dist;
|
|
|
|
nummapplanes += 2;
|
|
|
|
// allways put axial planes facing positive first
|
|
if ( p->type < 3 ) {
|
|
if ( p->normal[0] < 0 || p->normal[1] < 0 || p->normal[2] < 0 ) {
|
|
// flip order
|
|
temp = *p;
|
|
*p = *( p + 1 );
|
|
*( p + 1 ) = temp;
|
|
|
|
AddPlaneToHash( p );
|
|
AddPlaneToHash( p + 1 );
|
|
return nummapplanes - 1;
|
|
}
|
|
}
|
|
|
|
AddPlaneToHash( p );
|
|
AddPlaneToHash( p + 1 );
|
|
return nummapplanes - 2;
|
|
}
|
|
|
|
/*
|
|
==============
|
|
SnapVector
|
|
==============
|
|
*/
|
|
void SnapVector( vec3_t normal ){
|
|
int i;
|
|
|
|
for ( i = 0 ; i < 3 ; i++ )
|
|
{
|
|
if ( fabs( normal[i] - 1 ) < NORMAL_EPSILON ) {
|
|
VectorClear( normal );
|
|
normal[i] = 1;
|
|
break;
|
|
}
|
|
if ( fabs( normal[i] - -1 ) < NORMAL_EPSILON ) {
|
|
VectorClear( normal );
|
|
normal[i] = -1;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
/*
|
|
==============
|
|
SnapPlane
|
|
==============
|
|
*/
|
|
void SnapPlane( vec3_t normal, vec_t *dist ){
|
|
SnapVector( normal );
|
|
|
|
if ( fabs( *dist - Q_rint( *dist ) ) < DIST_EPSILON ) {
|
|
*dist = Q_rint( *dist );
|
|
}
|
|
}
|
|
|
|
/*
|
|
=============
|
|
FindFloatPlane
|
|
|
|
=============
|
|
*/
|
|
#ifndef USE_HASHING
|
|
int FindFloatPlane( vec3_t normal, vec_t dist ){
|
|
int i;
|
|
plane_t *p;
|
|
|
|
SnapPlane( normal, &dist );
|
|
for ( i = 0, p = mapplanes ; i < nummapplanes ; i++, p++ )
|
|
{
|
|
if ( PlaneEqual( p, normal, dist ) ) {
|
|
return i;
|
|
}
|
|
}
|
|
|
|
return CreateNewFloatPlane( normal, dist );
|
|
}
|
|
#else
|
|
int FindFloatPlane( vec3_t normal, vec_t dist ){
|
|
int i;
|
|
plane_t *p;
|
|
int hash, h;
|
|
|
|
SnapPlane( normal, &dist );
|
|
hash = (int)fabs( dist ) / 8;
|
|
hash &= ( PLANE_HASHES - 1 );
|
|
|
|
// search the border bins as well
|
|
for ( i = -1 ; i <= 1 ; i++ )
|
|
{
|
|
h = ( hash + i ) & ( PLANE_HASHES - 1 );
|
|
for ( p = planehash[h] ; p ; p = p->hash_chain )
|
|
{
|
|
if ( PlaneEqual( p, normal, dist ) ) {
|
|
return p - mapplanes;
|
|
}
|
|
}
|
|
}
|
|
|
|
return CreateNewFloatPlane( normal, dist );
|
|
}
|
|
#endif
|
|
|
|
/*
|
|
================
|
|
PlaneFromPoints
|
|
================
|
|
*/
|
|
int PlaneFromPoints( int *p0, int *p1, int *p2 ){
|
|
vec3_t t1, t2, normal;
|
|
vec_t dist;
|
|
|
|
VectorSubtract( p0, p1, t1 );
|
|
VectorSubtract( p2, p1, t2 );
|
|
CrossProduct( t1, t2, normal );
|
|
VectorNormalize( normal, normal );
|
|
|
|
dist = DotProduct( p0, normal );
|
|
|
|
return FindFloatPlane( normal, dist );
|
|
}
|
|
|
|
|
|
//====================================================================
|
|
|
|
|
|
/*
|
|
===========
|
|
BrushContents
|
|
===========
|
|
*/
|
|
int BrushContents( mapbrush_t *b ){
|
|
int contents;
|
|
side_t *s;
|
|
int i;
|
|
int trans;
|
|
|
|
s = &b->original_sides[0];
|
|
contents = s->contents;
|
|
trans = texinfo[s->texinfo].flags;
|
|
for ( i = 1 ; i < b->numsides ; i++, s++ )
|
|
{
|
|
s = &b->original_sides[i];
|
|
trans |= texinfo[s->texinfo].flags;
|
|
if ( s->contents != contents ) {
|
|
Sys_Printf( "Entity %i, Brush %i: mixed face contents\n"
|
|
, b->entitynum, b->brushnum );
|
|
break;
|
|
}
|
|
}
|
|
|
|
// if any side is translucent, mark the contents
|
|
// and change solid to window
|
|
if ( trans & ( SURF_TRANS33 | SURF_TRANS66 ) ) {
|
|
contents |= CONTENTS_TRANSLUCENT;
|
|
if ( contents & CONTENTS_SOLID ) {
|
|
contents &= ~CONTENTS_SOLID;
|
|
contents |= CONTENTS_WINDOW;
|
|
}
|
|
}
|
|
|
|
return contents;
|
|
}
|
|
|
|
|
|
//============================================================================
|
|
|
|
/*
|
|
=================
|
|
AddBrushBevels
|
|
|
|
Adds any additional planes necessary to allow the brush to be expanded
|
|
against axial bounding boxes
|
|
=================
|
|
*/
|
|
void AddBrushBevels( mapbrush_t *b ){
|
|
int axis, dir;
|
|
int i, j, k, l, order;
|
|
side_t sidetemp;
|
|
brush_texture_t tdtemp;
|
|
side_t *s, *s2;
|
|
vec3_t normal;
|
|
float dist;
|
|
winding_t *w, *w2;
|
|
vec3_t vec, vec2;
|
|
float d;
|
|
|
|
//
|
|
// add the axial planes
|
|
//
|
|
order = 0;
|
|
for ( axis = 0 ; axis < 3 ; axis++ )
|
|
{
|
|
for ( dir = -1 ; dir <= 1 ; dir += 2, order++ )
|
|
{
|
|
// see if the plane is allready present
|
|
for ( i = 0, s = b->original_sides ; i < b->numsides ; i++,s++ )
|
|
{
|
|
if ( mapplanes[s->planenum].normal[axis] == dir ) {
|
|
break;
|
|
}
|
|
}
|
|
|
|
if ( i == b->numsides ) { // add a new side
|
|
if ( nummapbrushsides == MAX_MAP_BRUSHSIDES ) {
|
|
Error( "MAX_MAP_BRUSHSIDES" );
|
|
}
|
|
nummapbrushsides++;
|
|
b->numsides++;
|
|
VectorClear( normal );
|
|
normal[axis] = dir;
|
|
if ( dir == 1 ) {
|
|
dist = b->maxs[axis];
|
|
}
|
|
else{
|
|
dist = -b->mins[axis];
|
|
}
|
|
s->planenum = FindFloatPlane( normal, dist );
|
|
s->texinfo = b->original_sides[0].texinfo;
|
|
s->contents = b->original_sides[0].contents;
|
|
s->bevel = true;
|
|
c_boxbevels++;
|
|
}
|
|
|
|
// if the plane is not in it canonical order, swap it
|
|
if ( i != order ) {
|
|
sidetemp = b->original_sides[order];
|
|
b->original_sides[order] = b->original_sides[i];
|
|
b->original_sides[i] = sidetemp;
|
|
|
|
j = b->original_sides - brushsides;
|
|
tdtemp = side_brushtextures[j + order];
|
|
side_brushtextures[j + order] = side_brushtextures[j + i];
|
|
side_brushtextures[j + i] = tdtemp;
|
|
}
|
|
}
|
|
}
|
|
|
|
//
|
|
// add the edge bevels
|
|
//
|
|
if ( b->numsides == 6 ) {
|
|
return; // pure axial
|
|
|
|
}
|
|
// test the non-axial plane edges
|
|
for ( i = 6 ; i < b->numsides ; i++ )
|
|
{
|
|
s = b->original_sides + i;
|
|
w = s->winding;
|
|
if ( !w ) {
|
|
continue;
|
|
}
|
|
for ( j = 0 ; j < w->numpoints ; j++ )
|
|
{
|
|
k = ( j + 1 ) % w->numpoints;
|
|
VectorSubtract( w->p[j], w->p[k], vec );
|
|
if ( VectorNormalize( vec, vec ) < 0.5 ) {
|
|
continue;
|
|
}
|
|
SnapVector( vec );
|
|
for ( k = 0 ; k < 3 ; k++ )
|
|
if ( vec[k] == -1 || vec[k] == 1 ) {
|
|
break;
|
|
} // axial
|
|
if ( k != 3 ) {
|
|
continue; // only test non-axial edges
|
|
|
|
}
|
|
// try the six possible slanted axials from this edge
|
|
for ( axis = 0 ; axis < 3 ; axis++ )
|
|
{
|
|
for ( dir = -1 ; dir <= 1 ; dir += 2 )
|
|
{
|
|
// construct a plane
|
|
VectorClear( vec2 );
|
|
vec2[axis] = dir;
|
|
CrossProduct( vec, vec2, normal );
|
|
if ( VectorNormalize( normal, normal ) < 0.5 ) {
|
|
continue;
|
|
}
|
|
dist = DotProduct( w->p[j], normal );
|
|
|
|
// if all the points on all the sides are
|
|
// behind this plane, it is a proper edge bevel
|
|
for ( k = 0 ; k < b->numsides ; k++ )
|
|
{
|
|
// if this plane has allready been used, skip it
|
|
if ( PlaneEqual( &mapplanes[b->original_sides[k].planenum]
|
|
, normal, dist ) ) {
|
|
break;
|
|
}
|
|
|
|
w2 = b->original_sides[k].winding;
|
|
if ( !w2 ) {
|
|
continue;
|
|
}
|
|
for ( l = 0 ; l < w2->numpoints ; l++ )
|
|
{
|
|
d = DotProduct( w2->p[l], normal ) - dist;
|
|
if ( d > 0.1 ) {
|
|
break; // point in front
|
|
}
|
|
}
|
|
if ( l != w2->numpoints ) {
|
|
break;
|
|
}
|
|
}
|
|
|
|
if ( k != b->numsides ) {
|
|
continue; // wasn't part of the outer hull
|
|
}
|
|
// add this plane
|
|
if ( nummapbrushsides == MAX_MAP_BRUSHSIDES ) {
|
|
Error( "MAX_MAP_BRUSHSIDES" );
|
|
}
|
|
nummapbrushsides++;
|
|
s2 = &b->original_sides[b->numsides];
|
|
s2->planenum = FindFloatPlane( normal, dist );
|
|
s2->texinfo = b->original_sides[0].texinfo;
|
|
s2->contents = b->original_sides[0].contents;
|
|
s2->bevel = true;
|
|
c_edgebevels++;
|
|
b->numsides++;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
/*
|
|
================
|
|
MakeBrushWindings
|
|
|
|
makes basewindigs for sides and mins / maxs for the brush
|
|
================
|
|
*/
|
|
qboolean MakeBrushWindings( mapbrush_t *ob ){
|
|
int i, j;
|
|
winding_t *w;
|
|
side_t *side;
|
|
plane_t *plane;
|
|
|
|
ClearBounds( ob->mins, ob->maxs );
|
|
|
|
for ( i = 0 ; i < ob->numsides ; i++ )
|
|
{
|
|
plane = &mapplanes[ob->original_sides[i].planenum];
|
|
w = BaseWindingForPlane( plane->normal, plane->dist );
|
|
for ( j = 0 ; j < ob->numsides && w; j++ )
|
|
{
|
|
if ( i == j ) {
|
|
continue;
|
|
}
|
|
if ( ob->original_sides[j].bevel ) {
|
|
continue;
|
|
}
|
|
plane = &mapplanes[ob->original_sides[j].planenum ^ 1];
|
|
ChopWindingInPlace( &w, plane->normal, plane->dist, 0 ); //CLIP_EPSILON);
|
|
}
|
|
|
|
side = &ob->original_sides[i];
|
|
side->winding = w;
|
|
if ( w ) {
|
|
side->visible = true;
|
|
for ( j = 0 ; j < w->numpoints ; j++ )
|
|
AddPointToBounds( w->p[j], ob->mins, ob->maxs );
|
|
}
|
|
}
|
|
|
|
for ( i = 0 ; i < 3 ; i++ )
|
|
{
|
|
if ( ob->mins[0] < -4096 || ob->maxs[0] > 4096 ) {
|
|
Sys_Printf( "entity %i, brush %i: bounds out of range\n", ob->entitynum, ob->brushnum );
|
|
}
|
|
if ( ob->mins[0] > 4096 || ob->maxs[0] < -4096 ) {
|
|
Sys_Printf( "entity %i, brush %i: no visible sides on brush\n", ob->entitynum, ob->brushnum );
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
|
|
/*
|
|
=================
|
|
ParseBrush
|
|
=================
|
|
*/
|
|
void ParseBrush( entity_t *mapent ){
|
|
mapbrush_t *b;
|
|
int i,j, k;
|
|
int mt;
|
|
side_t *side, *s2;
|
|
int planenum;
|
|
brush_texture_t td;
|
|
int planepts[3][3];
|
|
|
|
if ( nummapbrushes == MAX_MAP_BRUSHES ) {
|
|
Error( "nummapbrushes == MAX_MAP_BRUSHES" );
|
|
}
|
|
|
|
b = &mapbrushes[nummapbrushes];
|
|
b->original_sides = &brushsides[nummapbrushsides];
|
|
b->entitynum = num_entities - 1;
|
|
b->brushnum = nummapbrushes - mapent->firstbrush;
|
|
|
|
do
|
|
{
|
|
if ( !GetToken( true ) ) {
|
|
break;
|
|
}
|
|
if ( !strcmp( token, "}" ) ) {
|
|
break;
|
|
}
|
|
|
|
if ( nummapbrushsides == MAX_MAP_BRUSHSIDES ) {
|
|
Error( "MAX_MAP_BRUSHSIDES" );
|
|
}
|
|
side = &brushsides[nummapbrushsides];
|
|
|
|
// read the three point plane definition
|
|
for ( i = 0 ; i < 3 ; i++ )
|
|
{
|
|
if ( i != 0 ) {
|
|
GetToken( true );
|
|
}
|
|
if ( strcmp( token, "(" ) ) {
|
|
Error( "parsing brush" );
|
|
}
|
|
|
|
for ( j = 0 ; j < 3 ; j++ )
|
|
{
|
|
GetToken( false );
|
|
planepts[i][j] = atoi( token );
|
|
}
|
|
|
|
GetToken( false );
|
|
if ( strcmp( token, ")" ) ) {
|
|
Error( "parsing brush" );
|
|
}
|
|
|
|
}
|
|
|
|
|
|
//
|
|
// read the texturedef
|
|
//
|
|
GetToken( false );
|
|
strcpy( td.name, token );
|
|
|
|
GetToken( false );
|
|
td.shift[0] = atoi( token );
|
|
GetToken( false );
|
|
td.shift[1] = atoi( token );
|
|
GetToken( false );
|
|
td.rotate = atoi( token );
|
|
GetToken( false );
|
|
td.scale[0] = atof( token );
|
|
GetToken( false );
|
|
td.scale[1] = atof( token );
|
|
|
|
// find default flags and values
|
|
mt = FindMiptex( td.name );
|
|
td.flags = textureref[mt].flags;
|
|
td.value = textureref[mt].value;
|
|
side->contents = textureref[mt].contents;
|
|
side->surf = td.flags = textureref[mt].flags;
|
|
|
|
if ( TokenAvailable() ) {
|
|
GetToken( false );
|
|
side->contents = atoi( token );
|
|
GetToken( false );
|
|
side->surf = td.flags = atoi( token );
|
|
GetToken( false );
|
|
td.value = atoi( token );
|
|
}
|
|
|
|
// translucent objects are automatically classified as detail
|
|
if ( side->surf & ( SURF_TRANS33 | SURF_TRANS66 ) ) {
|
|
side->contents |= CONTENTS_DETAIL;
|
|
}
|
|
if ( side->contents & ( CONTENTS_PLAYERCLIP | CONTENTS_MONSTERCLIP ) ) {
|
|
side->contents |= CONTENTS_DETAIL;
|
|
}
|
|
if ( fulldetail ) {
|
|
side->contents &= ~CONTENTS_DETAIL;
|
|
}
|
|
if ( !( side->contents & ( ( LAST_VISIBLE_CONTENTS - 1 )
|
|
| CONTENTS_PLAYERCLIP | CONTENTS_MONSTERCLIP | CONTENTS_MIST ) ) ) {
|
|
side->contents |= CONTENTS_SOLID;
|
|
}
|
|
|
|
// hints and skips are never detail, and have no content
|
|
if ( side->surf & ( SURF_HINT | SURF_SKIP ) ) {
|
|
side->contents = 0;
|
|
side->surf &= ~CONTENTS_DETAIL;
|
|
}
|
|
|
|
|
|
//
|
|
// find the plane number
|
|
//
|
|
planenum = PlaneFromPoints( planepts[0], planepts[1], planepts[2] );
|
|
if ( planenum == -1 ) {
|
|
Sys_Printf( "Entity %i, Brush %i: plane with no normal\n"
|
|
, b->entitynum, b->brushnum );
|
|
continue;
|
|
}
|
|
|
|
//
|
|
// see if the plane has been used already
|
|
//
|
|
for ( k = 0 ; k < b->numsides ; k++ )
|
|
{
|
|
s2 = b->original_sides + k;
|
|
if ( s2->planenum == planenum ) {
|
|
Sys_Printf( "Entity %i, Brush %i: duplicate plane\n"
|
|
, b->entitynum, b->brushnum );
|
|
break;
|
|
}
|
|
if ( s2->planenum == ( planenum ^ 1 ) ) {
|
|
Sys_Printf( "Entity %i, Brush %i: mirrored plane\n"
|
|
, b->entitynum, b->brushnum );
|
|
break;
|
|
}
|
|
}
|
|
if ( k != b->numsides ) {
|
|
continue; // duplicated
|
|
|
|
}
|
|
//
|
|
// keep this side
|
|
//
|
|
|
|
side = b->original_sides + b->numsides;
|
|
side->planenum = planenum;
|
|
side->texinfo = TexinfoForBrushTexture( &mapplanes[planenum],
|
|
&td, vec3_origin );
|
|
|
|
// save the td off in case there is an origin brush and we
|
|
// have to recalculate the texinfo
|
|
side_brushtextures[nummapbrushsides] = td;
|
|
|
|
nummapbrushsides++;
|
|
b->numsides++;
|
|
} while ( 1 );
|
|
|
|
// get the content for the entire brush
|
|
b->contents = BrushContents( b );
|
|
|
|
// allow detail brushes to be removed
|
|
if ( nodetail && ( b->contents & CONTENTS_DETAIL ) ) {
|
|
b->numsides = 0;
|
|
return;
|
|
}
|
|
|
|
// allow water brushes to be removed
|
|
if ( nowater && ( b->contents & ( CONTENTS_LAVA | CONTENTS_SLIME | CONTENTS_WATER ) ) ) {
|
|
b->numsides = 0;
|
|
return;
|
|
}
|
|
|
|
// create windings for sides and bounds for brush
|
|
MakeBrushWindings( b );
|
|
|
|
// brushes that will not be visible at all will never be
|
|
// used as bsp splitters
|
|
if ( b->contents & ( CONTENTS_PLAYERCLIP | CONTENTS_MONSTERCLIP ) ) {
|
|
c_clipbrushes++;
|
|
for ( i = 0 ; i < b->numsides ; i++ )
|
|
b->original_sides[i].texinfo = TEXINFO_NODE;
|
|
}
|
|
|
|
//
|
|
// origin brushes are removed, but they set
|
|
// the rotation origin for the rest of the brushes
|
|
// in the entity. After the entire entity is parsed,
|
|
// the planenums and texinfos will be adjusted for
|
|
// the origin brush
|
|
//
|
|
if ( b->contents & CONTENTS_ORIGIN ) {
|
|
char string[32];
|
|
vec3_t origin;
|
|
|
|
if ( num_entities == 1 ) {
|
|
Error( "Entity %i, Brush %i: origin brushes not allowed in world"
|
|
, b->entitynum, b->brushnum );
|
|
return;
|
|
}
|
|
|
|
VectorAdd( b->mins, b->maxs, origin );
|
|
VectorScale( origin, 0.5, origin );
|
|
|
|
sprintf( string, "%i %i %i", (int)origin[0], (int)origin[1], (int)origin[2] );
|
|
SetKeyValue( &entities[b->entitynum], "origin", string );
|
|
|
|
VectorCopy( origin, entities[b->entitynum].origin );
|
|
|
|
// don't keep this brush
|
|
b->numsides = 0;
|
|
|
|
return;
|
|
}
|
|
|
|
AddBrushBevels( b );
|
|
|
|
nummapbrushes++;
|
|
mapent->numbrushes++;
|
|
}
|
|
|
|
/*
|
|
================
|
|
MoveBrushesToWorld
|
|
|
|
Takes all of the brushes from the current entity and
|
|
adds them to the world's brush list.
|
|
|
|
Used by func_group and func_areaportal
|
|
================
|
|
*/
|
|
void MoveBrushesToWorld( entity_t *mapent ){
|
|
int newbrushes;
|
|
int worldbrushes;
|
|
mapbrush_t *temp;
|
|
int i;
|
|
|
|
// this is pretty gross, because the brushes are expected to be
|
|
// in linear order for each entity
|
|
|
|
newbrushes = mapent->numbrushes;
|
|
worldbrushes = entities[0].numbrushes;
|
|
|
|
temp = malloc( newbrushes * sizeof( mapbrush_t ) );
|
|
memcpy( temp, mapbrushes + mapent->firstbrush, newbrushes * sizeof( mapbrush_t ) );
|
|
|
|
#if 0 // let them keep their original brush numbers
|
|
for ( i = 0 ; i < newbrushes ; i++ )
|
|
temp[i].entitynum = 0;
|
|
#endif
|
|
|
|
// make space to move the brushes (overlapped copy)
|
|
memmove( mapbrushes + worldbrushes + newbrushes,
|
|
mapbrushes + worldbrushes,
|
|
sizeof( mapbrush_t ) * ( nummapbrushes - worldbrushes - newbrushes ) );
|
|
|
|
// copy the new brushes down
|
|
memcpy( mapbrushes + worldbrushes, temp, sizeof( mapbrush_t ) * newbrushes );
|
|
|
|
// fix up indexes
|
|
entities[0].numbrushes += newbrushes;
|
|
for ( i = 1 ; i < num_entities ; i++ )
|
|
entities[i].firstbrush += newbrushes;
|
|
free( temp );
|
|
|
|
mapent->numbrushes = 0;
|
|
}
|
|
|
|
/*
|
|
================
|
|
ParseMapEntity
|
|
================
|
|
*/
|
|
qboolean ParseMapEntity( void ){
|
|
entity_t *mapent;
|
|
epair_t *e;
|
|
side_t *s;
|
|
int i, j;
|
|
int startbrush, startsides;
|
|
vec_t newdist;
|
|
mapbrush_t *b;
|
|
|
|
if ( !GetToken( true ) ) {
|
|
return false;
|
|
}
|
|
|
|
if ( strcmp( token, "{" ) ) {
|
|
Error( "ParseEntity: { not found" );
|
|
}
|
|
|
|
if ( num_entities == MAX_MAP_ENTITIES ) {
|
|
Error( "num_entities == MAX_MAP_ENTITIES" );
|
|
}
|
|
|
|
startbrush = nummapbrushes;
|
|
startsides = nummapbrushsides;
|
|
|
|
mapent = &entities[num_entities];
|
|
num_entities++;
|
|
memset( mapent, 0, sizeof( *mapent ) );
|
|
mapent->firstbrush = nummapbrushes;
|
|
mapent->numbrushes = 0;
|
|
// mapent->portalareas[0] = -1;
|
|
// mapent->portalareas[1] = -1;
|
|
|
|
do
|
|
{
|
|
if ( !GetToken( true ) ) {
|
|
Error( "ParseEntity: EOF without closing brace" );
|
|
}
|
|
if ( !strcmp( token, "}" ) ) {
|
|
break;
|
|
}
|
|
if ( !strcmp( token, "{" ) ) {
|
|
ParseBrush( mapent );
|
|
}
|
|
else
|
|
{
|
|
e = ParseEpair();
|
|
e->next = mapent->epairs;
|
|
mapent->epairs = e;
|
|
}
|
|
} while ( 1 );
|
|
|
|
GetVectorForKey( mapent, "origin", mapent->origin );
|
|
|
|
//
|
|
// if there was an origin brush, offset all of the planes and texinfo
|
|
//
|
|
if ( mapent->origin[0] || mapent->origin[1] || mapent->origin[2] ) {
|
|
for ( i = 0 ; i < mapent->numbrushes ; i++ )
|
|
{
|
|
b = &mapbrushes[mapent->firstbrush + i];
|
|
for ( j = 0 ; j < b->numsides ; j++ )
|
|
{
|
|
s = &b->original_sides[j];
|
|
newdist = mapplanes[s->planenum].dist -
|
|
DotProduct( mapplanes[s->planenum].normal, mapent->origin );
|
|
s->planenum = FindFloatPlane( mapplanes[s->planenum].normal, newdist );
|
|
s->texinfo = TexinfoForBrushTexture( &mapplanes[s->planenum],
|
|
&side_brushtextures[s - brushsides], mapent->origin );
|
|
}
|
|
MakeBrushWindings( b );
|
|
}
|
|
}
|
|
|
|
// group entities are just for editor convenience
|
|
// toss all brushes into the world entity
|
|
if ( !strcmp( "func_group", ValueForKey( mapent, "classname" ) ) ) {
|
|
MoveBrushesToWorld( mapent );
|
|
mapent->numbrushes = 0;
|
|
return true;
|
|
}
|
|
|
|
// areaportal entities move their brushes, but don't eliminate
|
|
// the entity
|
|
if ( !strcmp( "func_areaportal", ValueForKey( mapent, "classname" ) ) ) {
|
|
char str[128];
|
|
|
|
if ( mapent->numbrushes != 1 ) {
|
|
Error( "Entity %i: func_areaportal can only be a single brush", num_entities - 1 );
|
|
}
|
|
|
|
b = &mapbrushes[nummapbrushes - 1];
|
|
b->contents = CONTENTS_AREAPORTAL;
|
|
c_areaportals++;
|
|
mapent->areaportalnum = c_areaportals;
|
|
// set the portal number as "style"
|
|
sprintf( str, "%i", c_areaportals );
|
|
SetKeyValue( mapent, "style", str );
|
|
MoveBrushesToWorld( mapent );
|
|
return true;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
//===================================================================
|
|
|
|
/*
|
|
================
|
|
LoadMapFile
|
|
================
|
|
*/
|
|
void LoadMapFile( char *filename ){
|
|
int i;
|
|
|
|
Sys_FPrintf( SYS_VRB, "--- LoadMapFile ---\n" );
|
|
|
|
LoadScriptFile( filename );
|
|
|
|
nummapbrushsides = 0;
|
|
num_entities = 0;
|
|
|
|
while ( ParseMapEntity() )
|
|
{
|
|
}
|
|
|
|
ClearBounds( map_mins, map_maxs );
|
|
for ( i = 0 ; i < entities[0].numbrushes ; i++ )
|
|
{
|
|
if ( mapbrushes[i].mins[0] > 4096 ) {
|
|
continue; // no valid points
|
|
}
|
|
AddPointToBounds( mapbrushes[i].mins, map_mins, map_maxs );
|
|
AddPointToBounds( mapbrushes[i].maxs, map_mins, map_maxs );
|
|
}
|
|
|
|
Sys_FPrintf( SYS_VRB, "%5i brushes\n", nummapbrushes );
|
|
Sys_FPrintf( SYS_VRB, "%5i clipbrushes\n", c_clipbrushes );
|
|
Sys_FPrintf( SYS_VRB, "%5i total sides\n", nummapbrushsides );
|
|
Sys_FPrintf( SYS_VRB, "%5i boxbevels\n", c_boxbevels );
|
|
Sys_FPrintf( SYS_VRB, "%5i edgebevels\n", c_edgebevels );
|
|
Sys_FPrintf( SYS_VRB, "%5i entities\n", num_entities );
|
|
Sys_FPrintf( SYS_VRB, "%5i planes\n", nummapplanes );
|
|
Sys_FPrintf( SYS_VRB, "%5i areaportals\n", c_areaportals );
|
|
Sys_FPrintf( SYS_VRB, "size: %5.0f,%5.0f,%5.0f to %5.0f,%5.0f,%5.0f\n", map_mins[0],map_mins[1],map_mins[2],
|
|
map_maxs[0],map_maxs[1],map_maxs[2] );
|
|
|
|
// TestExpandBrushes ();
|
|
}
|
|
|
|
|
|
//====================================================================
|
|
|
|
|
|
/*
|
|
================
|
|
TestExpandBrushes
|
|
|
|
Expands all the brush planes and saves a new map out
|
|
================
|
|
*/
|
|
void TestExpandBrushes( void ){
|
|
FILE *f;
|
|
side_t *s;
|
|
int i, j, bn;
|
|
winding_t *w;
|
|
char *name = "expanded.map";
|
|
mapbrush_t *brush;
|
|
vec_t dist;
|
|
|
|
Sys_Printf( "writing %s\n", name );
|
|
f = fopen( name, "wb" );
|
|
if ( !f ) {
|
|
Error( "Can't write %s\b", name );
|
|
}
|
|
|
|
fprintf( f, "{\n\"classname\" \"worldspawn\"\n" );
|
|
|
|
for ( bn = 0 ; bn < nummapbrushes ; bn++ )
|
|
{
|
|
brush = &mapbrushes[bn];
|
|
fprintf( f, "{\n" );
|
|
for ( i = 0 ; i < brush->numsides ; i++ )
|
|
{
|
|
s = brush->original_sides + i;
|
|
dist = mapplanes[s->planenum].dist;
|
|
for ( j = 0 ; j < 3 ; j++ )
|
|
dist += fabs( 16 * mapplanes[s->planenum].normal[j] );
|
|
|
|
w = BaseWindingForPlane( mapplanes[s->planenum].normal, dist );
|
|
|
|
fprintf( f,"( %i %i %i ) ", (int)w->p[0][0], (int)w->p[0][1], (int)w->p[0][2] );
|
|
fprintf( f,"( %i %i %i ) ", (int)w->p[1][0], (int)w->p[1][1], (int)w->p[1][2] );
|
|
fprintf( f,"( %i %i %i ) ", (int)w->p[2][0], (int)w->p[2][1], (int)w->p[2][2] );
|
|
|
|
fprintf( f, "%s 0 0 0 1 1\n", texinfo[s->texinfo].texture );
|
|
FreeWinding( w );
|
|
}
|
|
fprintf( f, "}\n" );
|
|
}
|
|
fprintf( f, "}\n" );
|
|
|
|
fclose( f );
|
|
|
|
Error( "can't proceed after expanding brushes" );
|
|
}
|