1352 lines
29 KiB
C
1352 lines
29 KiB
C
// map.c
|
|
|
|
#include "qbsp.h"
|
|
|
|
#include <assert.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
|
|
|
|
=============================================================================
|
|
*/
|
|
|
|
|
|
void PrintBrushMsg(mapbrush_t *b, char *msg)
|
|
{
|
|
if (b == NULL)
|
|
{
|
|
printf("%s\n", msg);
|
|
return;
|
|
}
|
|
|
|
printf("BRUSH- %s: Entity#%i (%2.4f,%2.4f,%2.f)\n",
|
|
msg, b->entitynum, entities[b->entitynum].origin[0], entities[b->entitynum].origin[1], entities[b->entitynum].origin[2]);
|
|
printf(" Brush#%i min=(%2.4f,%2.4f,%2.4f) max=(%2.4f,%2.4f,%2.4f)\n",
|
|
b->brushnum, mapbrushes[b->brushnum].mins[0], mapbrushes[b->brushnum].mins[1], mapbrushes[b->brushnum].mins[2],
|
|
b->brushnum, mapbrushes[b->brushnum].maxs[0], mapbrushes[b->brushnum].maxs[1], mapbrushes[b->brushnum].maxs[2]);
|
|
}
|
|
|
|
|
|
/*
|
|
=================
|
|
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);
|
|
if (VectorNormalize (normal, normal) < 0.5)
|
|
return (-1);
|
|
|
|
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)
|
|
{
|
|
PrintBrushMsg(b, "Mixed face contents");
|
|
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;
|
|
}
|
|
}
|
|
|
|
if (trans & SURF_ALPHA_TEXTURE)
|
|
{
|
|
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)
|
|
PrintBrushMsg(ob, "Bounds out of range");
|
|
if (ob->mins[0] > 4096 || ob->maxs[0] < -4096)
|
|
PrintBrushMsg(ob, "No visible sides on brush");
|
|
}
|
|
|
|
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 (!GetScriptToken (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)
|
|
GetScriptToken (true);
|
|
if (strcmp (token, "(") )
|
|
Error ("parsing brush");
|
|
|
|
for (j=0 ; j<3 ; j++)
|
|
{
|
|
GetScriptToken (false);
|
|
planepts[i][j] = atoi(token);
|
|
}
|
|
|
|
GetScriptToken (false);
|
|
if (strcmp (token, ")") )
|
|
Error ("parsing brush");
|
|
|
|
}
|
|
|
|
|
|
//
|
|
// read the texturedef
|
|
//
|
|
GetScriptToken (false);
|
|
strcpy (td.name, token);
|
|
|
|
GetScriptToken (false);
|
|
td.shift[0] = atoi(token);
|
|
GetScriptToken (false);
|
|
td.shift[1] = atoi(token);
|
|
GetScriptToken (false);
|
|
td.rotate = atoi(token);
|
|
GetScriptToken (false);
|
|
td.scale[0] = atof(token);
|
|
GetScriptToken (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 (ScriptTokenAvailable())
|
|
{
|
|
GetScriptToken (false);
|
|
side->contents = atoi(token);
|
|
GetScriptToken (false);
|
|
side->surf = td.flags = atoi(token);
|
|
GetScriptToken (false);
|
|
td.value = atoi(token);
|
|
}
|
|
|
|
if (ScriptTokenAvailable())
|
|
{
|
|
GetScriptToken (false);
|
|
side->lighting.r = atof(token)*255.0;
|
|
GetScriptToken (false);
|
|
side->lighting.g = atof(token)*255.0;
|
|
GetScriptToken (false);
|
|
side->lighting.b = atof(token)*255.0;
|
|
GetScriptToken (false);
|
|
side->lighting.a = atof(token)*255.0;
|
|
}
|
|
|
|
// 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)
|
|
{
|
|
PrintBrushMsg(b, "Plane with no normal");
|
|
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)
|
|
{
|
|
PrintBrushMsg(b, "Duplicate plane");
|
|
break;
|
|
}
|
|
if ( s2->planenum == (planenum^1) )
|
|
{
|
|
PrintBrushMsg(b, "Mirrored plane");
|
|
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)
|
|
{
|
|
PrintBrushMsg(b, "Origin brushes error.");
|
|
Error ("Origin brushes not allowed in world");
|
|
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));
|
|
if (temp == NULL)
|
|
Error("MoveBrushesToWorld MALLOC failed! Could not allocate %s bytes.", 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 (!GetScriptToken (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 (!GetScriptToken (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;
|
|
}
|
|
|
|
//===================================================================
|
|
|
|
static char *msgNames;
|
|
static int *msgNameOffsets;
|
|
static int numMsgs;
|
|
|
|
#define MAX_MESSAGE_NAME_SIZE 64
|
|
|
|
void LoadMessageNames()
|
|
{
|
|
int filesize;
|
|
char *buf;
|
|
int count = 0;
|
|
char path[1024];
|
|
|
|
sprintf(path, "%scinematics/messages.txt", gamedir);
|
|
|
|
filesize = LoadFile(path, &buf);
|
|
|
|
if(filesize)
|
|
{
|
|
int i;
|
|
int numRead;
|
|
char msg[MAX_MESSAGE_NAME_SIZE+1];
|
|
|
|
sscanf(buf + count, "%d%n", &numMsgs, &numRead);
|
|
count += numRead;
|
|
|
|
msgNameOffsets = malloc(numMsgs * sizeof(int));
|
|
if (msgNameOffsets == NULL)
|
|
Error("LoadMessageNames MALLOC failed! Could not allocate %s bytes.", numMsgs*sizeof(int));
|
|
|
|
for(i = 0; i < numMsgs; ++i)
|
|
{
|
|
assert(buf[count] == '\r');
|
|
|
|
++count;
|
|
|
|
assert(buf[count] == '\n');
|
|
|
|
++count;
|
|
|
|
msgNameOffsets[i] = count;
|
|
|
|
sscanf(buf + count, "%s%n", msg, &numRead);
|
|
count += numRead;
|
|
}
|
|
|
|
msgNames = buf;
|
|
}
|
|
else
|
|
{
|
|
msgNames = NULL;
|
|
}
|
|
}
|
|
|
|
void FreeMessageNames()
|
|
{
|
|
free(msgNames);
|
|
free(msgNameOffsets);
|
|
}
|
|
|
|
_inline void WriteBuf(char *buf, int *count, void *toWrite, size_t size)
|
|
{
|
|
memcpy(buf + *count, toWrite, size);
|
|
*count += size;
|
|
}
|
|
|
|
#define ENT_NAME_MAX 128
|
|
|
|
void ProcessCinematicScript(char *name)
|
|
{
|
|
int filesize;
|
|
char *buf;
|
|
char *buf2;
|
|
int count = 0;
|
|
int count2 = 0;
|
|
char path[1024];
|
|
void *e = NULL;
|
|
|
|
#if 1
|
|
if(!msgNames)
|
|
{
|
|
assert(0);
|
|
return;
|
|
}
|
|
|
|
sprintf(path, "%scinematics/%s.txt", gamedir, name);
|
|
|
|
filesize = LoadFile(path, &buf);
|
|
|
|
if(filesize)
|
|
{
|
|
buf2 = malloc(filesize+1);
|
|
if (buf2 == NULL)
|
|
Error("ProcessCinematicScript (1) MALLOC failed! Could not allocate %s bytes.", filesize+1);
|
|
|
|
while(1)
|
|
{
|
|
int i, frame, msgID, numRead;
|
|
char msg[MAX_MESSAGE_NAME_SIZE+1];
|
|
char entityName[ENT_NAME_MAX+1];
|
|
|
|
sscanf(buf + count, "%d %s %s%n", &frame, entityName, msg, &numRead);
|
|
count += numRead;
|
|
|
|
WriteBuf(buf2, &count2, &frame, sizeof(int));
|
|
|
|
WriteBuf(buf2, &count2, entityName, strlen(entityName) + 1);
|
|
|
|
msgID = -1;
|
|
|
|
for(i = 0; i < numMsgs; ++i)
|
|
{
|
|
if(!strnicmp(msgNames + msgNameOffsets[i], msg, strlen(msg)))
|
|
{
|
|
msgID = i;
|
|
break;
|
|
}
|
|
}
|
|
|
|
assert(msgID != -1);
|
|
|
|
WriteBuf(buf2, &count2, &msgID, sizeof(int));
|
|
|
|
assert(buf[count] == '\t');
|
|
|
|
++count;
|
|
|
|
if((buf[count] != '\t') && (buf[count] != '\n'))
|
|
{
|
|
char format[17]; // should need more than 16 parms, +1 for null
|
|
int curFormat = 0;
|
|
char current;
|
|
byte *b;
|
|
short *s;
|
|
int *i;
|
|
float *f;
|
|
double *d;
|
|
char ent[ENT_NAME_MAX+1];
|
|
|
|
sscanf(buf + count, "%s%n", format, &numRead);
|
|
count += numRead;
|
|
|
|
WriteBuf(buf2, &count2, &format, strlen(format) + 1);
|
|
|
|
while(current = format[curFormat])
|
|
{
|
|
switch(current)
|
|
{
|
|
case 'b':
|
|
sscanf(buf + count, "%b%n", &b, &numRead);
|
|
count += numRead;
|
|
|
|
WriteBuf(buf2, &count2, &b, sizeof(byte));
|
|
break;
|
|
case 's':
|
|
sscanf(buf + count, "%s%n", &s, &numRead);
|
|
count += numRead;
|
|
|
|
WriteBuf(buf2, &count2, &s, sizeof(short));
|
|
break;
|
|
case 'i':
|
|
sscanf(buf + count, "%d%n", &i, &numRead);
|
|
count += numRead;
|
|
|
|
WriteBuf(buf2, &count2, &i, sizeof(int));
|
|
break;
|
|
case 'f':
|
|
sscanf(buf + count, "%f%n", &f, &numRead);
|
|
count += numRead;
|
|
|
|
*d = *f;
|
|
|
|
WriteBuf(buf2, &count2, &d, sizeof(double));
|
|
break;
|
|
case 'e':
|
|
assert(buf[count] == '\t');
|
|
|
|
++count;
|
|
|
|
if((buf[count] != '\t') && (buf[count] != '\n'))
|
|
{
|
|
sscanf(buf + count, "%s%n", ent, &numRead);
|
|
count += numRead;
|
|
}
|
|
else
|
|
{
|
|
ent[0] = '\0';
|
|
}
|
|
|
|
WriteBuf(buf2, &count2, &ent, strlen(ent) + 1);
|
|
break;
|
|
case 'c': // color is in hex
|
|
sscanf(buf + count, "%x%n", &i, &numRead);
|
|
count += numRead;
|
|
|
|
WriteBuf(buf2, &count2, &i, sizeof(int));
|
|
break;
|
|
default:
|
|
assert(0);
|
|
}
|
|
|
|
++curFormat;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
char noFormat = '\0';
|
|
|
|
WriteBuf(buf2, &count2, &noFormat, sizeof(char));
|
|
}
|
|
|
|
while((buf[count] != '\n') && (buf[count] != EOF))
|
|
{
|
|
assert(buf[count] == '\t' || buf[count] == '\r');
|
|
|
|
++count;
|
|
}
|
|
|
|
if(count == filesize - 1)
|
|
{
|
|
break;
|
|
}
|
|
|
|
assert(count < filesize - 1);
|
|
}
|
|
|
|
sprintf(path, "%scinematics/%s.ics", gamedir, name); // ingame cinematic script
|
|
|
|
SaveFile (path, buf2, count);
|
|
free(buf2);
|
|
}
|
|
|
|
free(buf);
|
|
#else
|
|
{
|
|
int i;
|
|
short s;
|
|
char *c;
|
|
char *entityName;
|
|
size_t size;
|
|
|
|
buf2 = malloc(filesize+1);
|
|
if (buf2 == NULL)
|
|
Error("ProcessCinematicScript (2) MALLOC failed! Could not allocate %s bytes.", filesize+1);
|
|
|
|
size = sizeof(int);
|
|
i = 10; // frame 10
|
|
memcpy(buf2 + count, &i, size);
|
|
count += size;
|
|
|
|
size = sizeof(char)*5;
|
|
entityName = "test";
|
|
memcpy(buf2 + count, entityName, size);
|
|
count += size;
|
|
|
|
size = sizeof(int);
|
|
i = 9; // MSG_DEATH
|
|
memcpy(buf2 + count, &i, size);
|
|
count += size;
|
|
|
|
size = sizeof(char)*5;
|
|
c = "eeei"; // four parms
|
|
memcpy(buf2 + count, c, size);
|
|
count += size;
|
|
|
|
size = sizeof(int);
|
|
i = 0;
|
|
memcpy(buf2 + count, &i, size);
|
|
count += size;
|
|
|
|
size = sizeof(int);
|
|
i = 0;
|
|
memcpy(buf2 + count, &i, size);
|
|
count += size;
|
|
|
|
size = sizeof(int);
|
|
i = 0;
|
|
memcpy(buf2 + count, &i, size);
|
|
count += size;
|
|
|
|
size = sizeof(int);
|
|
i = 100;
|
|
memcpy(buf2 + count, &i, size);
|
|
count += size;
|
|
|
|
sprintf(path, "%scinematics/%s.ics", gamedir, name); // ingame cinematic script
|
|
|
|
SaveFile (path, buf2, count);
|
|
free(buf2);
|
|
}
|
|
#endif
|
|
}
|
|
|
|
/*
|
|
================
|
|
LoadMapFile
|
|
================
|
|
*/
|
|
void LoadMapFile (char *filename)
|
|
{
|
|
int i;
|
|
|
|
qprintf ("--- LoadMapFile ---\n");
|
|
|
|
LoadScriptFile (filename);
|
|
|
|
nummapbrushsides = 0;
|
|
num_entities = 0;
|
|
|
|
while (ParseMapEntity ())
|
|
{
|
|
}
|
|
|
|
#if 1
|
|
for(i = 0; i < num_entities; ++i)
|
|
{
|
|
epair_t *e;
|
|
|
|
e = entities[i].epairs;
|
|
|
|
while(e)
|
|
{
|
|
if(stricmp(e->key, "cinematic") == 0)
|
|
{ // found a cinematic script
|
|
ProcessCinematicScript(e->value);
|
|
break;
|
|
}
|
|
|
|
e = e->next;
|
|
}
|
|
}
|
|
#else
|
|
ProcessCinematicScript("test");
|
|
#endif
|
|
|
|
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);
|
|
}
|
|
|
|
qprintf ("%5i brushes\n", nummapbrushes);
|
|
qprintf ("%5i clipbrushes\n", c_clipbrushes);
|
|
qprintf ("%5i total sides\n", nummapbrushsides);
|
|
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]);
|
|
|
|
// 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;
|
|
|
|
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");
|
|
}
|