mirror of
https://github.com/DrBeef/ioq3quest.git
synced 2024-11-25 13:31:10 +00:00
1251 lines
26 KiB
C
1251 lines
26 KiB
C
/*
|
|
===========================================================================
|
|
Copyright (C) 1999-2005 Id Software, Inc.
|
|
|
|
This file is part of Quake III Arena source code.
|
|
|
|
Quake III Arena source code 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.
|
|
|
|
Quake III Arena source code 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 Foobar; if not, write to the Free Software
|
|
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
|
===========================================================================
|
|
*/
|
|
// map.c
|
|
|
|
#include "qbsp.h"
|
|
|
|
|
|
int entitySourceBrushes; // to track editor brush numbers
|
|
|
|
int numMapPatches;
|
|
|
|
// undefine to make plane finding use linear sort
|
|
#define USE_HASHING
|
|
#define PLANE_HASHES 1024
|
|
plane_t *planehash[PLANE_HASHES];
|
|
|
|
plane_t mapplanes[MAX_MAP_PLANES];
|
|
int nummapplanes;
|
|
|
|
// as brushes and patches are read in, the shaders are stored out in order
|
|
// here, so -onlytextures can just copy them out over the existing shaders
|
|
// in the drawSurfaces
|
|
char mapIndexedShaders[MAX_MAP_BRUSHSIDES][MAX_QPATH];
|
|
int numMapIndexedShaders;
|
|
|
|
vec3_t map_mins, map_maxs;
|
|
|
|
entity_t *mapent;
|
|
|
|
|
|
|
|
int c_boxbevels;
|
|
int c_edgebevels;
|
|
|
|
int c_areaportals;
|
|
int c_detail;
|
|
int c_structural;
|
|
|
|
// brushes are parsed into a temporary array of sides,
|
|
// which will have the bevels added and duplicates
|
|
// removed before the final brush is allocated
|
|
bspbrush_t *buildBrush;
|
|
|
|
|
|
void TestExpandBrushes (void);
|
|
void SetTerrainTextures( void );
|
|
void ParseTerrain( void );
|
|
|
|
|
|
/*
|
|
=============================================================================
|
|
|
|
PLANE FINDING
|
|
|
|
=============================================================================
|
|
*/
|
|
|
|
|
|
/*
|
|
================
|
|
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 qtrue;
|
|
#else
|
|
if (p->normal[0] == normal[0]
|
|
&& p->normal[1] == normal[1]
|
|
&& p->normal[2] == normal[2]
|
|
&& p->dist == dist)
|
|
return qtrue;
|
|
#endif
|
|
return qfalse;
|
|
}
|
|
|
|
/*
|
|
================
|
|
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)
|
|
{
|
|
_printf( "FloatPlane: bad normal\n");
|
|
return -1;
|
|
}
|
|
|
|
// 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
|
|
|
|
/*
|
|
================
|
|
MapPlaneFromPoints
|
|
================
|
|
*/
|
|
int MapPlaneFromPoints (vec3_t p0, vec3_t p1, vec3_t 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);
|
|
}
|
|
|
|
|
|
//====================================================================
|
|
|
|
/*
|
|
===========
|
|
SetBrushContents
|
|
|
|
The contents on all sides of a brush should be the same
|
|
Sets contentsShader, contents, opaque, and detail
|
|
===========
|
|
*/
|
|
void SetBrushContents( bspbrush_t *b ) {
|
|
int contents, c2;
|
|
side_t *s;
|
|
int i;
|
|
qboolean mixed;
|
|
int allFlags;
|
|
|
|
s = &b->sides[0];
|
|
contents = s->contents;
|
|
b->contentShader = s->shaderInfo;
|
|
mixed = qfalse;
|
|
|
|
allFlags = 0;
|
|
|
|
for ( i=1 ; i<b->numsides ; i++, s++ ) {
|
|
s = &b->sides[i];
|
|
|
|
if ( !s->shaderInfo ) {
|
|
continue;
|
|
}
|
|
|
|
c2 = s->contents;
|
|
if (c2 != contents) {
|
|
mixed = qtrue;
|
|
}
|
|
|
|
allFlags |= s->surfaceFlags;
|
|
}
|
|
|
|
if ( mixed ) {
|
|
qprintf ("Entity %i, Brush %i: mixed face contents\n"
|
|
, b->entitynum, b->brushnum);
|
|
}
|
|
|
|
if ( ( contents & CONTENTS_DETAIL ) && ( contents & CONTENTS_STRUCTURAL ) ) {
|
|
_printf ("Entity %i, Brush %i: mixed CONTENTS_DETAIL and CONTENTS_STRUCTURAL\n"
|
|
, num_entities-1, entitySourceBrushes );
|
|
contents &= ~CONTENTS_DETAIL;
|
|
}
|
|
|
|
// the fulldetail flag will cause detail brushes to be
|
|
// treated like normal brushes
|
|
if ( fulldetail ) {
|
|
contents &= ~CONTENTS_DETAIL;
|
|
}
|
|
|
|
// all translucent brushes that aren't specirically made structural will
|
|
// be detail
|
|
if ( ( contents & CONTENTS_TRANSLUCENT ) && !( contents & CONTENTS_STRUCTURAL ) ) {
|
|
contents |= CONTENTS_DETAIL;
|
|
}
|
|
|
|
if ( contents & CONTENTS_DETAIL ) {
|
|
c_detail++;
|
|
b->detail = qtrue;
|
|
} else {
|
|
c_structural++;
|
|
b->detail = qfalse;
|
|
}
|
|
|
|
if ( contents & CONTENTS_TRANSLUCENT ) {
|
|
b->opaque = qfalse;
|
|
} else {
|
|
b->opaque = qtrue;
|
|
}
|
|
|
|
if ( contents & CONTENTS_AREAPORTAL ) {
|
|
c_areaportals++;
|
|
}
|
|
|
|
b->contents = contents;
|
|
}
|
|
|
|
|
|
//============================================================================
|
|
|
|
/*
|
|
=================
|
|
AddBrushBevels
|
|
|
|
Adds any additional planes necessary to allow the brush being
|
|
built to be expanded against axial bounding boxes
|
|
=================
|
|
*/
|
|
void AddBrushBevels( void ) {
|
|
int axis, dir;
|
|
int i, order;
|
|
side_t sidetemp;
|
|
side_t *s;
|
|
vec3_t normal;
|
|
float dist;
|
|
|
|
//
|
|
// 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=buildBrush->sides ; i < buildBrush->numsides ; i++,s++ ) {
|
|
if (mapplanes[s->planenum].normal[axis] == dir)
|
|
break;
|
|
}
|
|
|
|
if (i == buildBrush->numsides )
|
|
{ // add a new side
|
|
if ( buildBrush->numsides == MAX_BUILD_SIDES ) {
|
|
Error( "MAX_BUILD_SIDES" );
|
|
}
|
|
memset( s, 0, sizeof( *s ) );
|
|
buildBrush->numsides++;
|
|
VectorClear (normal);
|
|
normal[axis] = dir;
|
|
if (dir == 1)
|
|
dist = buildBrush->maxs[axis];
|
|
else
|
|
dist = -buildBrush->mins[axis];
|
|
s->planenum = FindFloatPlane (normal, dist);
|
|
s->contents = buildBrush->sides[0].contents;
|
|
s->bevel = qtrue;
|
|
c_boxbevels++;
|
|
}
|
|
|
|
// if the plane is not in it canonical order, swap it
|
|
if (i != order)
|
|
{
|
|
sidetemp = buildBrush->sides[order];
|
|
buildBrush->sides[order] = buildBrush->sides[i];
|
|
buildBrush->sides[i] = sidetemp;
|
|
}
|
|
}
|
|
}
|
|
|
|
//
|
|
// add the edge bevels
|
|
//
|
|
if ( buildBrush->numsides == 6 ) {
|
|
return; // pure axial
|
|
} else {
|
|
int j, k, l;
|
|
float d;
|
|
winding_t *w, *w2;
|
|
side_t *s2;
|
|
vec3_t vec, vec2;
|
|
|
|
// test the non-axial plane edges
|
|
// this code tends to cause some problems...
|
|
for (i=6 ; i<buildBrush->numsides ; i++)
|
|
{
|
|
s = buildBrush->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 < buildBrush->numsides ; k++)
|
|
{
|
|
// if this plane has allready been used, skip it
|
|
if (PlaneEqual (&mapplanes[buildBrush->sides[k].planenum]
|
|
, normal, dist) )
|
|
break;
|
|
|
|
w2 = buildBrush->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 != buildBrush->numsides)
|
|
continue; // wasn't part of the outer hull
|
|
// add this plane
|
|
if ( buildBrush->numsides == MAX_BUILD_SIDES ) {
|
|
Error( "MAX_BUILD_SIDES" );
|
|
}
|
|
|
|
s2 = &buildBrush->sides[buildBrush->numsides];
|
|
buildBrush->numsides++;
|
|
memset( s2, 0, sizeof( *s2 ) );
|
|
|
|
s2->planenum = FindFloatPlane (normal, dist);
|
|
s2->contents = buildBrush->sides[0].contents;
|
|
s2->bevel = qtrue;
|
|
c_edgebevels++;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/*
|
|
===============
|
|
AddBackSides
|
|
|
|
fog volumes need to have inside faces created
|
|
===============
|
|
*/
|
|
void AddBackSides( void ) {
|
|
/*
|
|
bspbrush_t *b;
|
|
int i, originalSides;
|
|
side_t *s;
|
|
side_t *newSide;
|
|
|
|
b = buildBrush;
|
|
originalSides = b->numsides;
|
|
for ( i = 0 ; i < originalSides ; i++ ) {
|
|
s = &b->sides[i];
|
|
if ( !s->shaderInfo ) {
|
|
continue;
|
|
}
|
|
if ( !(s->shaderInfo->contents & CONTENTS_FOG) ) {
|
|
continue;
|
|
}
|
|
|
|
// duplicate the up-facing side
|
|
if ( mapplanes[ s->planenum ].normal[2] == 1 ) {
|
|
newSide = &b->sides[ b->numsides ];
|
|
b->numsides++;
|
|
|
|
*newSide = *s;
|
|
newSide->backSide = qtrue;
|
|
newSide->planenum = s->planenum ^ 1; // opposite side
|
|
}
|
|
}
|
|
*/
|
|
}
|
|
|
|
/*
|
|
===============
|
|
FinishBrush
|
|
|
|
Produces a final brush based on the buildBrush->sides array
|
|
and links it to the current entity
|
|
===============
|
|
*/
|
|
bspbrush_t *FinishBrush( void ) {
|
|
bspbrush_t *b;
|
|
|
|
// liquids may need to have extra sides created for back sides
|
|
AddBackSides();
|
|
|
|
// create windings for sides and bounds for brush
|
|
if ( !CreateBrushWindings( buildBrush ) ) {
|
|
// don't keep this brush
|
|
return NULL;
|
|
}
|
|
|
|
// brushes that will not be visible at all are forced to be detail
|
|
if ( buildBrush->contents & (CONTENTS_PLAYERCLIP|CONTENTS_MONSTERCLIP) )
|
|
{
|
|
buildBrush->detail = qtrue;
|
|
c_detail++;
|
|
}
|
|
|
|
//
|
|
// 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 ( buildBrush->contents & CONTENTS_ORIGIN )
|
|
{
|
|
char string[32];
|
|
vec3_t origin;
|
|
|
|
if (num_entities == 1) {
|
|
_printf ("Entity %i, Brush %i: origin brushes not allowed in world\n"
|
|
, num_entities - 1, entitySourceBrushes);
|
|
return NULL;
|
|
}
|
|
|
|
VectorAdd (buildBrush->mins, buildBrush->maxs, origin);
|
|
VectorScale (origin, 0.5, origin);
|
|
|
|
sprintf (string, "%i %i %i", (int)origin[0], (int)origin[1], (int)origin[2]);
|
|
SetKeyValue (&entities[num_entities - 1], "origin", string);
|
|
|
|
VectorCopy (origin, entities[num_entities - 1].origin);
|
|
|
|
// don't keep this brush
|
|
return NULL;
|
|
}
|
|
|
|
if ( buildBrush->contents & CONTENTS_AREAPORTAL ) {
|
|
if (num_entities != 1) {
|
|
_printf ("Entity %i, Brush %i: areaportals only allowed in world\n"
|
|
, num_entities - 1, entitySourceBrushes);
|
|
return NULL;
|
|
}
|
|
}
|
|
|
|
AddBrushBevels ();
|
|
|
|
// keep it
|
|
b = CopyBrush( buildBrush );
|
|
|
|
b->entitynum = num_entities-1;
|
|
b->brushnum = entitySourceBrushes;
|
|
|
|
b->original = b;
|
|
|
|
b->next = mapent->brushes;
|
|
mapent->brushes = b;
|
|
|
|
return b;
|
|
}
|
|
|
|
//======================================================================
|
|
|
|
|
|
/*
|
|
==================
|
|
textureAxisFromPlane
|
|
==================
|
|
*/
|
|
vec3_t baseaxis[18] =
|
|
{
|
|
{0,0,1}, {1,0,0}, {0,-1,0}, // floor
|
|
{0,0,-1}, {1,0,0}, {0,-1,0}, // ceiling
|
|
{1,0,0}, {0,1,0}, {0,0,-1}, // west wall
|
|
{-1,0,0}, {0,1,0}, {0,0,-1}, // east wall
|
|
{0,1,0}, {1,0,0}, {0,0,-1}, // south wall
|
|
{0,-1,0}, {1,0,0}, {0,0,-1} // north wall
|
|
};
|
|
|
|
void TextureAxisFromPlane(plane_t *pln, vec3_t xv, vec3_t yv)
|
|
{
|
|
int bestaxis;
|
|
vec_t dot,best;
|
|
int i;
|
|
|
|
best = 0;
|
|
bestaxis = 0;
|
|
|
|
for (i=0 ; i<6 ; i++)
|
|
{
|
|
dot = DotProduct (pln->normal, baseaxis[i*3]);
|
|
if (dot > best)
|
|
{
|
|
best = dot;
|
|
bestaxis = i;
|
|
}
|
|
}
|
|
|
|
VectorCopy (baseaxis[bestaxis*3+1], xv);
|
|
VectorCopy (baseaxis[bestaxis*3+2], yv);
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
=================
|
|
QuakeTextureVecs
|
|
|
|
Creates world-to-texture mapping vecs for crappy quake plane arrangements
|
|
=================
|
|
*/
|
|
void QuakeTextureVecs( plane_t *plane, vec_t shift[2], vec_t rotate, vec_t scale[2],
|
|
vec_t mappingVecs[2][4] ) {
|
|
|
|
vec3_t vecs[2];
|
|
int sv, tv;
|
|
vec_t ang, sinv, cosv;
|
|
vec_t ns, nt;
|
|
int i, j;
|
|
|
|
TextureAxisFromPlane(plane, vecs[0], vecs[1]);
|
|
|
|
if (!scale[0])
|
|
scale[0] = 1;
|
|
if (!scale[1])
|
|
scale[1] = 1;
|
|
|
|
// rotate axis
|
|
if (rotate == 0)
|
|
{ sinv = 0 ; cosv = 1; }
|
|
else if (rotate == 90)
|
|
{ sinv = 1 ; cosv = 0; }
|
|
else if (rotate == 180)
|
|
{ sinv = 0 ; cosv = -1; }
|
|
else if (rotate == 270)
|
|
{ sinv = -1 ; cosv = 0; }
|
|
else
|
|
{
|
|
ang = rotate / 180 * Q_PI;
|
|
sinv = sin(ang);
|
|
cosv = cos(ang);
|
|
}
|
|
|
|
if (vecs[0][0])
|
|
sv = 0;
|
|
else if (vecs[0][1])
|
|
sv = 1;
|
|
else
|
|
sv = 2;
|
|
|
|
if (vecs[1][0])
|
|
tv = 0;
|
|
else if (vecs[1][1])
|
|
tv = 1;
|
|
else
|
|
tv = 2;
|
|
|
|
for (i=0 ; i<2 ; i++) {
|
|
ns = cosv * vecs[i][sv] - sinv * vecs[i][tv];
|
|
nt = sinv * vecs[i][sv] + cosv * vecs[i][tv];
|
|
vecs[i][sv] = ns;
|
|
vecs[i][tv] = nt;
|
|
}
|
|
|
|
for (i=0 ; i<2 ; i++)
|
|
for (j=0 ; j<3 ; j++)
|
|
mappingVecs[i][j] = vecs[i][j] / scale[i];
|
|
|
|
mappingVecs[0][3] = shift[0];
|
|
mappingVecs[1][3] = shift[1];
|
|
}
|
|
|
|
//======================================================================
|
|
|
|
/*
|
|
=================
|
|
ParseRawBrush
|
|
|
|
Just parses the sides into buildBrush->sides[], nothing else.
|
|
no validation, back plane removal, etc.
|
|
|
|
Timo - 08/26/99
|
|
added brush epairs parsing ( ignoring actually )
|
|
Timo - 08/04/99
|
|
added exclusive brush primitive parsing
|
|
Timo - 08/08/99
|
|
support for old brush format back in
|
|
NOTE : it would be "cleaner" to have seperate functions to parse between old and new brushes
|
|
=================
|
|
*/
|
|
void ParseRawBrush( ) {
|
|
side_t *side;
|
|
vec3_t planepts[3];
|
|
int planenum;
|
|
shaderInfo_t *si;
|
|
// old brushes
|
|
vec_t shift[2];
|
|
vec_t rotate;
|
|
vec_t scale[2];
|
|
char name[MAX_QPATH];
|
|
char shader[MAX_QPATH];
|
|
int flags;
|
|
|
|
buildBrush->numsides = 0;
|
|
buildBrush->detail = qfalse;
|
|
|
|
if (g_bBrushPrimit==BPRIMIT_NEWBRUSHES)
|
|
MatchToken( "{" );
|
|
|
|
do
|
|
{
|
|
if (!GetToken (qtrue))
|
|
break;
|
|
if (!strcmp (token, "}") )
|
|
break;
|
|
//Timo : brush primitive : here we may have to jump over brush epairs ( only used in editor )
|
|
if (g_bBrushPrimit==BPRIMIT_NEWBRUSHES)
|
|
{
|
|
do
|
|
{
|
|
if (strcmp (token, "(") )
|
|
GetToken( qfalse );
|
|
else
|
|
break;
|
|
GetToken( qtrue );
|
|
} while (1);
|
|
}
|
|
UnGetToken();
|
|
|
|
if ( buildBrush->numsides == MAX_BUILD_SIDES ) {
|
|
Error( "MAX_BUILD_SIDES" );
|
|
}
|
|
|
|
side = &buildBrush->sides[ buildBrush->numsides ];
|
|
memset( side, 0, sizeof( *side ) );
|
|
buildBrush->numsides++;
|
|
|
|
// read the three point plane definition
|
|
Parse1DMatrix( 3, planepts[0] );
|
|
Parse1DMatrix( 3, planepts[1] );
|
|
Parse1DMatrix( 3, planepts[2] );
|
|
|
|
if (g_bBrushPrimit==BPRIMIT_NEWBRUSHES)
|
|
// read the texture matrix
|
|
Parse2DMatrix( 2, 3, (float *)side->texMat );
|
|
|
|
// read the texturedef
|
|
GetToken (qfalse);
|
|
strcpy (name, token);
|
|
|
|
// save the shader name for retexturing
|
|
if ( numMapIndexedShaders == MAX_MAP_BRUSHSIDES ) {
|
|
Error( "MAX_MAP_BRUSHSIDES" );
|
|
}
|
|
strcpy( mapIndexedShaders[numMapIndexedShaders], name );
|
|
numMapIndexedShaders++;
|
|
|
|
if (g_bBrushPrimit==BPRIMIT_OLDBRUSHES)
|
|
{
|
|
GetToken (qfalse);
|
|
shift[0] = atoi(token);
|
|
GetToken (qfalse);
|
|
shift[1] = atoi(token);
|
|
GetToken (qfalse);
|
|
rotate = atoi(token);
|
|
GetToken (qfalse);
|
|
scale[0] = atof(token);
|
|
GetToken (qfalse);
|
|
scale[1] = atof(token);
|
|
}
|
|
|
|
// find default flags and values
|
|
sprintf( shader, "textures/%s", name );
|
|
si = ShaderInfoForShader( shader );
|
|
side->shaderInfo = si;
|
|
side->surfaceFlags = si->surfaceFlags;
|
|
side->value = si->value;
|
|
side->contents = si->contents;
|
|
|
|
// allow override of default flags and values
|
|
// in Q3, the only thing you can override is DETAIL
|
|
if (TokenAvailable())
|
|
{
|
|
GetToken (qfalse);
|
|
// side->contents = atoi(token);
|
|
flags = atoi(token);
|
|
if ( flags & CONTENTS_DETAIL ) {
|
|
side->contents |= CONTENTS_DETAIL;
|
|
}
|
|
|
|
GetToken (qfalse);
|
|
// td.flags = atoi(token);
|
|
|
|
GetToken (qfalse);
|
|
// td.value = atoi(token);
|
|
}
|
|
|
|
|
|
// find the plane number
|
|
planenum = MapPlaneFromPoints (planepts[0], planepts[1], planepts[2]);
|
|
side->planenum = planenum;
|
|
|
|
if (g_bBrushPrimit==BPRIMIT_OLDBRUSHES)
|
|
// get the texture mapping for this texturedef / plane combination
|
|
QuakeTextureVecs( &mapplanes[planenum], shift, rotate, scale, side->vecs );
|
|
|
|
} while (1);
|
|
|
|
if (g_bBrushPrimit==BPRIMIT_NEWBRUSHES)
|
|
{
|
|
UnGetToken();
|
|
MatchToken( "}" );
|
|
MatchToken( "}" );
|
|
}
|
|
}
|
|
|
|
/*
|
|
=================
|
|
RemoveDuplicateBrushPlanes
|
|
|
|
Returns false if the brush has a mirrored set of planes,
|
|
meaning it encloses no volume.
|
|
Also removes planes without any normal
|
|
=================
|
|
*/
|
|
qboolean RemoveDuplicateBrushPlanes( bspbrush_t * b ) {
|
|
int i, j, k;
|
|
side_t *sides;
|
|
|
|
sides = b->sides;
|
|
|
|
for ( i = 1 ; i < b->numsides ; i++ ) {
|
|
|
|
// check for a degenerate plane
|
|
if ( sides[i].planenum == -1) {
|
|
_printf ("Entity %i, Brush %i: degenerate plane\n"
|
|
, b->entitynum, b->brushnum);
|
|
// remove it
|
|
for ( k = i + 1 ; k < b->numsides ; k++ ) {
|
|
sides[k-1] = sides[k];
|
|
}
|
|
b->numsides--;
|
|
i--;
|
|
continue;
|
|
}
|
|
|
|
// check for duplication and mirroring
|
|
for ( j = 0 ; j < i ; j++ ) {
|
|
if ( sides[i].planenum == sides[j].planenum ) {
|
|
_printf ("Entity %i, Brush %i: duplicate plane\n"
|
|
, b->entitynum, b->brushnum);
|
|
// remove the second duplicate
|
|
for ( k = i + 1 ; k < b->numsides ; k++ ) {
|
|
sides[k-1] = sides[k];
|
|
}
|
|
b->numsides--;
|
|
i--;
|
|
break;
|
|
}
|
|
|
|
if ( sides[i].planenum == (sides[j].planenum ^ 1) ) {
|
|
// mirror plane, brush is invalid
|
|
_printf ("Entity %i, Brush %i: mirrored plane\n"
|
|
, b->entitynum, b->brushnum);
|
|
return qfalse;
|
|
}
|
|
}
|
|
}
|
|
return qtrue;
|
|
}
|
|
|
|
|
|
/*
|
|
=================
|
|
ParseBrush
|
|
|
|
qboolean parameter to true -> parse new brush primitive format ( else use old format )
|
|
=================
|
|
*/
|
|
void ParseBrush (void) {
|
|
bspbrush_t *b;
|
|
|
|
ParseRawBrush();
|
|
|
|
buildBrush->portalareas[0] = -1;
|
|
buildBrush->portalareas[1] = -1;
|
|
buildBrush->entitynum = num_entities-1;
|
|
buildBrush->brushnum = entitySourceBrushes;
|
|
|
|
// if there are mirrored planes, the entire brush is invalid
|
|
if ( !RemoveDuplicateBrushPlanes( buildBrush ) ) {
|
|
return;
|
|
}
|
|
|
|
// get the content for the entire brush
|
|
SetBrushContents( buildBrush );
|
|
|
|
// allow detail brushes to be removed
|
|
if (nodetail && (buildBrush->contents & CONTENTS_DETAIL) ) {
|
|
FreeBrush( buildBrush );
|
|
return;
|
|
}
|
|
|
|
// allow water brushes to be removed
|
|
if (nowater && (buildBrush->contents & (CONTENTS_LAVA | CONTENTS_SLIME | CONTENTS_WATER)) ) {
|
|
FreeBrush( buildBrush );
|
|
return;
|
|
}
|
|
|
|
b = FinishBrush( );
|
|
if ( !b ) {
|
|
return;
|
|
}
|
|
}
|
|
|
|
|
|
/*
|
|
================
|
|
MoveBrushesToWorld
|
|
|
|
Takes all of the brushes from the current entity and
|
|
adds them to the world's brush list.
|
|
|
|
Used by func_group
|
|
================
|
|
*/
|
|
void MoveBrushesToWorld (entity_t *mapent) {
|
|
bspbrush_t *b, *next;
|
|
parseMesh_t *pm;
|
|
|
|
// move brushes
|
|
for ( b = mapent->brushes ; b ; b = next ) {
|
|
next = b->next;
|
|
|
|
b->next = entities[0].brushes;
|
|
entities[0].brushes = b;
|
|
}
|
|
mapent->brushes = NULL;
|
|
|
|
// move patches
|
|
if ( mapent->patches ) {
|
|
|
|
for ( pm = mapent->patches ; pm->next ; pm = pm->next ) {
|
|
}
|
|
|
|
pm->next = entities[0].patches;
|
|
entities[0].patches = mapent->patches;
|
|
|
|
mapent->patches = NULL;
|
|
}
|
|
}
|
|
|
|
|
|
/*
|
|
================
|
|
AdjustBrushesForOrigin
|
|
================
|
|
*/
|
|
void AdjustBrushesForOrigin( entity_t *ent ) {
|
|
bspbrush_t *b;
|
|
int i;
|
|
side_t *s;
|
|
vec_t newdist;
|
|
parseMesh_t *p;
|
|
|
|
for ( b = ent->brushes ; b ; b = b->next ) {
|
|
for (i=0 ; i<b->numsides ; i++) {
|
|
s = &b->sides[i];
|
|
newdist = mapplanes[s->planenum].dist -
|
|
DotProduct (mapplanes[s->planenum].normal, ent->origin);
|
|
s->planenum = FindFloatPlane (mapplanes[s->planenum].normal, newdist);
|
|
}
|
|
CreateBrushWindings(b);
|
|
}
|
|
|
|
for ( p = ent->patches ; p ; p = p->next ) {
|
|
for ( i = 0 ; i < p->mesh.width*p->mesh.height ; i++ ) {
|
|
VectorSubtract( p->mesh.verts[i].xyz, ent->origin, p->mesh.verts[i].xyz );
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
/*
|
|
================
|
|
ParseMapEntity
|
|
================
|
|
*/
|
|
qboolean ParseMapEntity (void) {
|
|
epair_t *e;
|
|
|
|
if (!GetToken (qtrue))
|
|
return qfalse;
|
|
|
|
if (strcmp (token, "{") )
|
|
{
|
|
Error ("ParseEntity: { not found, found %s on line %d - last entity was at: <%4.2f, %4.2f, %4.2f>...", token, scriptline, entities[num_entities].origin[0], entities[num_entities].origin[1], entities[num_entities].origin[2]);
|
|
}
|
|
|
|
if (num_entities == MAX_MAP_ENTITIES)
|
|
Error ("num_entities == MAX_MAP_ENTITIES");
|
|
|
|
entitySourceBrushes = 0;
|
|
|
|
mapent = &entities[num_entities];
|
|
num_entities++;
|
|
memset (mapent, 0, sizeof(*mapent));
|
|
|
|
do
|
|
{
|
|
if (!GetToken (qtrue))
|
|
Error ("ParseEntity: EOF without closing brace");
|
|
if (!strcmp (token, "}") )
|
|
break;
|
|
|
|
if (!strcmp (token, "{") ) {
|
|
// parse a brush or patch
|
|
if (!GetToken (qtrue))
|
|
break;
|
|
if ( !strcmp( token, "patchDef2" ) ) {
|
|
numMapPatches++;
|
|
ParsePatch();
|
|
} else if ( !strcmp( token, "terrainDef" ) ) {
|
|
ParseTerrain();
|
|
} else if ( !strcmp( token, "brushDef" ) ) {
|
|
if (g_bBrushPrimit==BPRIMIT_OLDBRUSHES)
|
|
Error("old brush format not allowed in new brush format map");
|
|
g_bBrushPrimit=BPRIMIT_NEWBRUSHES;
|
|
// parse brush primitive
|
|
ParseBrush();
|
|
}
|
|
else
|
|
{
|
|
if (g_bBrushPrimit==BPRIMIT_NEWBRUSHES)
|
|
Error("new brush format not allowed in old brush format map");
|
|
g_bBrushPrimit=BPRIMIT_OLDBRUSHES;
|
|
// parse old brush format
|
|
UnGetToken();
|
|
ParseBrush();
|
|
}
|
|
entitySourceBrushes++;
|
|
}
|
|
else
|
|
{
|
|
// parse a key / value pair
|
|
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
|
|
// for all the brushes in the entity
|
|
if (mapent->origin[0] || mapent->origin[1] || mapent->origin[2]) {
|
|
AdjustBrushesForOrigin( mapent );
|
|
}
|
|
|
|
// group_info entities are just for editor grouping
|
|
// ignored
|
|
// FIXME: leak!
|
|
if (!strcmp("group_info", ValueForKey (mapent, "classname")))
|
|
{
|
|
num_entities--;
|
|
return qtrue;
|
|
}
|
|
|
|
// group entities are just for editor convenience
|
|
// toss all brushes into the world entity
|
|
if (!strcmp ("func_group", ValueForKey (mapent, "classname")))
|
|
{
|
|
if ( !strcmp ("1", ValueForKey (mapent, "terrain"))) {
|
|
SetTerrainTextures();
|
|
}
|
|
MoveBrushesToWorld (mapent);
|
|
num_entities--;
|
|
return qtrue;
|
|
}
|
|
|
|
return qtrue;
|
|
}
|
|
|
|
//===================================================================
|
|
|
|
|
|
/*
|
|
================
|
|
LoadMapFile
|
|
================
|
|
*/
|
|
void LoadMapFile (char *filename) {
|
|
bspbrush_t *b;
|
|
|
|
qprintf ("--- LoadMapFile ---\n");
|
|
_printf ("Loading map file %s\n", filename);
|
|
|
|
LoadScriptFile (filename);
|
|
|
|
num_entities = 0;
|
|
numMapDrawSurfs = 0;
|
|
c_detail = 0;
|
|
|
|
g_bBrushPrimit = BPRIMIT_UNDEFINED;
|
|
|
|
// allocate a very large temporary brush for building
|
|
// the brushes as they are loaded
|
|
buildBrush = AllocBrush( MAX_BUILD_SIDES );
|
|
|
|
while (ParseMapEntity ())
|
|
{
|
|
}
|
|
|
|
ClearBounds (map_mins, map_maxs);
|
|
for ( b = entities[0].brushes ; b ; b=b->next ) {
|
|
AddPointToBounds( b->mins, map_mins, map_maxs );
|
|
AddPointToBounds( b->maxs, map_mins, map_maxs );
|
|
}
|
|
|
|
qprintf ("%5i total world brushes\n", CountBrushList( entities[0].brushes ) );
|
|
qprintf ("%5i detail brushes\n", c_detail );
|
|
qprintf ("%5i patches\n", numMapPatches);
|
|
qprintf ("%5i boxbevels\n", c_boxbevels);
|
|
qprintf ("%5i edgebevels\n", c_edgebevels);
|
|
qprintf ("%5i entities\n", num_entities);
|
|
qprintf ("%5i planes\n", nummapplanes);
|
|
qprintf ("%5i areaportals\n", c_areaportals);
|
|
qprintf ("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]);
|
|
|
|
if ( fakemap ) {
|
|
WriteBspBrushMap ("fakemap.map", entities[0].brushes );
|
|
}
|
|
|
|
if ( testExpand ) {
|
|
TestExpandBrushes ();
|
|
}
|
|
}
|
|
|
|
|
|
//====================================================================
|
|
|
|
|
|
/*
|
|
================
|
|
TestExpandBrushes
|
|
|
|
Expands all the brush planes and saves a new map out to
|
|
allow visual inspection of the clipping bevels
|
|
================
|
|
*/
|
|
void TestExpandBrushes( void ) {
|
|
side_t *s;
|
|
int i, j;
|
|
bspbrush_t *brush, *list, *copy;
|
|
vec_t dist;
|
|
plane_t *plane;
|
|
|
|
list = NULL;
|
|
|
|
for ( brush = entities[0].brushes ; brush ; brush = brush->next ) {
|
|
copy = CopyBrush( brush );
|
|
copy->next = list;
|
|
list = copy;
|
|
|
|
// expand all the planes
|
|
for ( i=0 ; i<brush->numsides ; i++ ) {
|
|
s = brush->sides + i;
|
|
plane = &mapplanes[s->planenum];
|
|
dist = plane->dist;
|
|
for (j=0 ; j<3 ; j++) {
|
|
dist += fabs( 16 * plane->normal[j] );
|
|
}
|
|
s->planenum = FindFloatPlane( plane->normal, dist );
|
|
}
|
|
|
|
}
|
|
|
|
WriteBspBrushMap ( "expanded.map", entities[0].brushes );
|
|
|
|
Error ("can't proceed after expanding brushes");
|
|
}
|