a4942abf52
git-svn-id: https://svn.code.sf.net/p/fteqw/code/trunk@5234 fc73d0e0-1445-4013-8a0c-d673dee63da5
492 lines
11 KiB
C
492 lines
11 KiB
C
/*
|
|
Copyright (C) 1996-1997 Id Software, Inc.
|
|
|
|
This program 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.
|
|
|
|
This program 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 this program; if not, write to the Free Software
|
|
Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
|
|
|
|
*/
|
|
|
|
#include "qtv.h"
|
|
|
|
#define MAX_MAP_LEAFS 32767
|
|
|
|
typedef struct {
|
|
float planen[3];
|
|
float planedist;
|
|
int child[2];
|
|
} node_t;
|
|
struct bsp_s {
|
|
unsigned int fullchecksum;
|
|
unsigned int visedchecksum;
|
|
node_t *nodes;
|
|
unsigned char *pvslump;
|
|
unsigned char **pvsofs;
|
|
|
|
unsigned char decpvs[(MAX_MAP_LEAFS+7)/8]; //decompressed pvs
|
|
int pvsbytecount;
|
|
|
|
|
|
|
|
int numintermissionspots;
|
|
intermission_t intermissionspot[8];
|
|
};
|
|
|
|
static const intermission_t nullintermissionspot = {{0}};
|
|
|
|
|
|
typedef struct
|
|
{
|
|
int contents;
|
|
int visofs; // -1 = no visibility info
|
|
|
|
short mins[3]; // for frustum culling
|
|
short maxs[3];
|
|
|
|
unsigned short firstmarksurface;
|
|
unsigned short nummarksurfaces;
|
|
#define NUM_AMBIENTS 4
|
|
unsigned char ambient_level[NUM_AMBIENTS];
|
|
} dleaf_t;
|
|
typedef struct
|
|
{
|
|
unsigned int planenum;
|
|
short children[2]; // negative numbers are -(leafs+1), not nodes
|
|
short mins[3]; // for sphere culling
|
|
short maxs[3];
|
|
unsigned short firstface;
|
|
unsigned short numfaces; // counting both sides
|
|
} dnode_t;
|
|
typedef struct
|
|
{
|
|
float normal[3];
|
|
float dist;
|
|
unsigned int type; // PLANE_X - PLANE_ANYZ ?remove? trivial to regenerate
|
|
} dplane_t;
|
|
typedef struct
|
|
{
|
|
int fileofs, filelen;
|
|
} lump_t;
|
|
#define LUMP_ENTITIES 0
|
|
#define LUMP_PLANES 1
|
|
#define LUMP_VISIBILITY 4
|
|
#define LUMP_NODES 5
|
|
#define LUMP_LEAFS 10
|
|
#define HEADER_LUMPS 15
|
|
typedef struct
|
|
{
|
|
int version;
|
|
lump_t lumps[HEADER_LUMPS];
|
|
} dheader_t;
|
|
|
|
|
|
|
|
void DecompressVis(unsigned char *in, unsigned char *out, int bytecount)
|
|
{
|
|
int c;
|
|
unsigned char *end;
|
|
|
|
for (end = out + bytecount; out < end; )
|
|
{
|
|
c = *in;
|
|
if (!c)
|
|
{ //a 0 is always followed by the count of 0s.
|
|
c = in[1];
|
|
in += 2;
|
|
for (; c > 4; c-=4)
|
|
{
|
|
*(unsigned int*)out = 0;
|
|
out+=4;
|
|
}
|
|
for (; c; c--)
|
|
*out++ = 0;
|
|
}
|
|
else
|
|
{
|
|
in++;
|
|
*out++ = c;
|
|
}
|
|
}
|
|
}
|
|
|
|
void BSP_LoadEntities(bsp_t *bsp, char *entitydata)
|
|
{
|
|
char *v;
|
|
char key[2048];
|
|
char value[2048];
|
|
|
|
enum {et_random, et_startspot, et_primarystart, et_intermission} etype;
|
|
|
|
float org[3];
|
|
float angles[3];
|
|
|
|
qboolean foundstartspot = false;
|
|
float startspotorg[3];
|
|
float startspotangles[3];
|
|
|
|
//char *COM_ParseToken (char *data, char *out, int outsize, const char *punctuation)
|
|
while (entitydata)
|
|
{
|
|
entitydata = COM_ParseToken(entitydata, key, sizeof(key), NULL);
|
|
if (!entitydata)
|
|
break;
|
|
|
|
if (!strcmp(key, "{"))
|
|
{
|
|
org[0] = 0;
|
|
org[1] = 0;
|
|
org[2] = 0;
|
|
|
|
angles[0] = 0;
|
|
angles[1] = 0;
|
|
angles[2] = 0;
|
|
etype = et_random;
|
|
|
|
for(;;)
|
|
{
|
|
|
|
if(!entitydata)
|
|
{
|
|
printf("unexpected eof in bsp entities section\n");
|
|
return;
|
|
}
|
|
|
|
entitydata = COM_ParseToken(entitydata, key, sizeof(key), NULL);
|
|
if (!strcmp(key, "}"))
|
|
break;
|
|
|
|
entitydata = COM_ParseToken(entitydata, value, sizeof(value), NULL);
|
|
|
|
if (!strcmp(key, "origin"))
|
|
{
|
|
v = value;
|
|
v = COM_ParseToken(v, key, sizeof(key), NULL);
|
|
org[0] = atof(key);
|
|
v = COM_ParseToken(v, key, sizeof(key), NULL);
|
|
org[1] = atof(key);
|
|
v = COM_ParseToken(v, key, sizeof(key), NULL);
|
|
org[2] = atof(key);
|
|
}
|
|
|
|
if (!strcmp(key, "angles") || !strcmp(key, "angle") || !strcmp(key, "mangle"))
|
|
{
|
|
v = value;
|
|
v = COM_ParseToken(v, key, sizeof(key), NULL);
|
|
angles[0] = atof(key);
|
|
v = COM_ParseToken(v, key, sizeof(key), NULL);
|
|
if (v)
|
|
{
|
|
angles[1] = atof(key);
|
|
v = COM_ParseToken(v, key, sizeof(key), NULL);
|
|
angles[2] = atof(key);
|
|
}
|
|
else
|
|
{
|
|
angles[1] = angles[0];
|
|
angles[0] = 0;
|
|
angles[2] = 0;
|
|
}
|
|
}
|
|
|
|
|
|
if (!strcmp(key, "classname"))
|
|
{
|
|
if (!strcmp(value, "info_player_start"))
|
|
etype = et_primarystart;
|
|
if (!strcmp(value, "info_deathmatch_start"))
|
|
etype = et_startspot;
|
|
if (!strcmp(value, "info_intermission"))
|
|
etype = et_intermission;
|
|
}
|
|
}
|
|
|
|
switch (etype)
|
|
{
|
|
case et_random: //a random (unknown) entity
|
|
break;
|
|
case et_primarystart: //a single player start
|
|
memcpy(startspotorg, org, sizeof(startspotorg));
|
|
memcpy(startspotangles, angles, sizeof(startspotangles));
|
|
foundstartspot = true;
|
|
break;
|
|
case et_startspot:
|
|
if (!foundstartspot)
|
|
{
|
|
memcpy(startspotorg, org, sizeof(startspotorg));
|
|
memcpy(startspotangles, angles, sizeof(startspotangles));
|
|
foundstartspot = true;
|
|
}
|
|
break;
|
|
case et_intermission:
|
|
if (bsp->numintermissionspots < sizeof(bsp->intermissionspot)/sizeof(bsp->intermissionspot[0]))
|
|
{
|
|
bsp->intermissionspot[bsp->numintermissionspots].pos[0] = org[0];
|
|
bsp->intermissionspot[bsp->numintermissionspots].pos[1] = org[1];
|
|
bsp->intermissionspot[bsp->numintermissionspots].pos[2] = org[2];
|
|
|
|
bsp->intermissionspot[bsp->numintermissionspots].angle[0] = angles[0];
|
|
bsp->intermissionspot[bsp->numintermissionspots].angle[1] = angles[1];
|
|
bsp->intermissionspot[bsp->numintermissionspots].angle[2] = angles[2];
|
|
bsp->numintermissionspots++;
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
printf("data not expected here\n");
|
|
return;
|
|
}
|
|
}
|
|
|
|
if (foundstartspot && !bsp->numintermissionspots)
|
|
{
|
|
bsp->intermissionspot[bsp->numintermissionspots].pos[0] = startspotorg[0];
|
|
bsp->intermissionspot[bsp->numintermissionspots].pos[1] = startspotorg[1];
|
|
bsp->intermissionspot[bsp->numintermissionspots].pos[2] = startspotorg[2];
|
|
|
|
bsp->intermissionspot[bsp->numintermissionspots].angle[0] = startspotangles[0];
|
|
bsp->intermissionspot[bsp->numintermissionspots].angle[1] = startspotangles[1];
|
|
bsp->intermissionspot[bsp->numintermissionspots].angle[2] = startspotangles[2];
|
|
bsp->numintermissionspots++;
|
|
}
|
|
}
|
|
|
|
bsp_t *BSP_LoadModel(cluster_t *cluster, char *gamedir, char *bspname)
|
|
{
|
|
unsigned char *data;
|
|
unsigned int size;
|
|
char *entdata;
|
|
|
|
dheader_t *header;
|
|
dplane_t *planes;
|
|
dnode_t *nodes;
|
|
dleaf_t *leaf;
|
|
|
|
int numnodes, i;
|
|
int numleafs;
|
|
unsigned int chksum;
|
|
|
|
bsp_t *bsp;
|
|
|
|
data = FS_ReadFile(gamedir, bspname, &size);
|
|
if (!data)
|
|
{
|
|
Sys_Printf(cluster, "Couldn't open bsp file \"%s\" (gamedir \"%s\")\n", bspname, gamedir);
|
|
return NULL;
|
|
}
|
|
|
|
|
|
header = (dheader_t*)data;
|
|
if (size < sizeof(dheader_t) || data[0] != 29)
|
|
{
|
|
free(data);
|
|
Sys_Printf(cluster, "BSP not version 29 (%s in %s)\n", bspname, gamedir);
|
|
return NULL;
|
|
}
|
|
|
|
for (i = 0; i < HEADER_LUMPS; i++)
|
|
{
|
|
if (LittleLong(header->lumps[i].fileofs) + LittleLong(header->lumps[i].filelen) > size)
|
|
{
|
|
free(data);
|
|
Sys_Printf(cluster, "BSP appears truncated (%s in gamedir %s)\n", bspname, gamedir);
|
|
return NULL;
|
|
}
|
|
}
|
|
|
|
planes = (dplane_t*)(data+LittleLong(header->lumps[LUMP_PLANES].fileofs));
|
|
nodes = (dnode_t*)(data+LittleLong(header->lumps[LUMP_NODES].fileofs));
|
|
leaf = (dleaf_t*)(data+LittleLong(header->lumps[LUMP_LEAFS].fileofs));
|
|
|
|
entdata = (char*)(data+LittleLong(header->lumps[LUMP_ENTITIES].fileofs));
|
|
|
|
numnodes = LittleLong(header->lumps[LUMP_NODES].filelen)/sizeof(dnode_t);
|
|
numleafs = LittleLong(header->lumps[LUMP_LEAFS].filelen)/sizeof(dleaf_t);
|
|
|
|
bsp = malloc(sizeof(bsp_t) + sizeof(node_t)*numnodes + LittleLong(header->lumps[LUMP_VISIBILITY].filelen) + sizeof(unsigned char *)*numleafs);
|
|
bsp->numintermissionspots = 0;
|
|
if (bsp)
|
|
{
|
|
bsp->fullchecksum = 0;
|
|
bsp->visedchecksum = 0;
|
|
for (i = 0; i < HEADER_LUMPS; i++)
|
|
{
|
|
if (i == LUMP_ENTITIES)
|
|
continue; //entities never appear in any checksums
|
|
|
|
chksum = Com_BlockChecksum(data + LittleLong(header->lumps[i].fileofs), LittleLong(header->lumps[i].filelen));
|
|
bsp->fullchecksum ^= chksum;
|
|
if (i == LUMP_VISIBILITY || i == LUMP_LEAFS || i == LUMP_NODES)
|
|
continue;
|
|
bsp->visedchecksum ^= chksum;
|
|
}
|
|
bsp->nodes = (node_t*)(bsp+1);
|
|
bsp->pvsofs = (unsigned char**)(bsp->nodes+numnodes);
|
|
bsp->pvslump = (unsigned char*)(bsp->pvsofs+numleafs);
|
|
|
|
bsp->pvsbytecount = (numleafs+7)/8;
|
|
|
|
for (i = 0; i < numnodes; i++)
|
|
{
|
|
bsp->nodes[i].child[0] = LittleShort(nodes[i].children[0]);
|
|
bsp->nodes[i].child[1] = LittleShort(nodes[i].children[1]);
|
|
bsp->nodes[i].planedist = planes[LittleLong(nodes[i].planenum)].dist;
|
|
bsp->nodes[i].planen[0] = planes[LittleLong(nodes[i].planenum)].normal[0];
|
|
bsp->nodes[i].planen[1] = planes[LittleLong(nodes[i].planenum)].normal[1];
|
|
bsp->nodes[i].planen[2] = planes[LittleLong(nodes[i].planenum)].normal[2];
|
|
}
|
|
memcpy(bsp->pvslump, data+LittleLong(header->lumps[LUMP_VISIBILITY].fileofs), LittleLong(header->lumps[LUMP_VISIBILITY].filelen));
|
|
|
|
for (i = 0; i < numleafs; i++)
|
|
{
|
|
if (leaf[i].visofs < 0)
|
|
bsp->pvsofs[i] = NULL;
|
|
else
|
|
bsp->pvsofs[i] = bsp->pvslump+leaf[i].visofs;
|
|
}
|
|
}
|
|
|
|
BSP_LoadEntities(bsp, entdata);
|
|
|
|
free(data);
|
|
|
|
return bsp;
|
|
}
|
|
|
|
void BSP_Free(bsp_t *bsp)
|
|
{
|
|
free(bsp);
|
|
}
|
|
|
|
int BSP_SphereLeafNums_r(bsp_t *bsp, int first, int maxleafs, unsigned short *list, float *pos, float radius)
|
|
{
|
|
node_t *node;
|
|
float dot;
|
|
int rn;
|
|
int numleafs = 0;
|
|
|
|
if (!bsp)
|
|
return 0;
|
|
|
|
for(rn = first;rn >= 0;)
|
|
{
|
|
node = &bsp->nodes[rn];
|
|
dot = (node->planen[0]*pos[0] + node->planen[1]*pos[1] + node->planen[2]*pos[2]) - node->planedist;
|
|
if (dot < -radius)
|
|
rn = node->child[1];
|
|
else if (dot > radius)
|
|
rn = node->child[0];
|
|
else
|
|
{
|
|
rn = BSP_SphereLeafNums_r(bsp, node->child[0], maxleafs-numleafs, list+numleafs, pos, radius);
|
|
if (rn < 0)
|
|
return -1; //ran out, so don't use pvs for this entity.
|
|
else
|
|
numleafs += rn;
|
|
rn = node->child[1]; //both sides
|
|
}
|
|
}
|
|
|
|
rn = -1-rn;
|
|
|
|
if (rn <= 0)
|
|
; //leaf 0 has no pvs info, so don't add it.
|
|
else if (maxleafs>numleafs)
|
|
{
|
|
list[numleafs] = rn-1;
|
|
numleafs++;
|
|
}
|
|
else
|
|
return -1; //there are just too many
|
|
|
|
return numleafs;
|
|
}
|
|
|
|
unsigned int BSP_Checksum(bsp_t *bsp)
|
|
{
|
|
if (!bsp)
|
|
return 0;
|
|
return bsp->visedchecksum;
|
|
}
|
|
|
|
int BSP_SphereLeafNums(bsp_t *bsp, int maxleafs, unsigned short *list, float x, float y, float z, float radius)
|
|
{
|
|
float pos[3];
|
|
pos[0] = x;
|
|
pos[1] = y;
|
|
pos[2] = z;
|
|
return BSP_SphereLeafNums_r(bsp, 0, maxleafs, list, pos, radius);
|
|
}
|
|
|
|
int BSP_LeafNum(bsp_t *bsp, float x, float y, float z)
|
|
{
|
|
node_t *node;
|
|
float dot;
|
|
int rn;
|
|
|
|
if (!bsp)
|
|
return 0;
|
|
|
|
for(rn = 0;rn >= 0;)
|
|
{
|
|
node = &bsp->nodes[rn];
|
|
dot = node->planen[0]*x + node->planen[1]*y + node->planen[2]*z;
|
|
rn = node->child[(dot-node->planedist) <= 0];
|
|
}
|
|
|
|
return -1-rn;
|
|
}
|
|
|
|
qboolean BSP_Visible(bsp_t *bsp, int leafcount, unsigned short *list)
|
|
{
|
|
int i;
|
|
if (!bsp)
|
|
return true;
|
|
|
|
if (leafcount < 0) //too many, so pvs was switched off.
|
|
return true;
|
|
|
|
for (i = 0; i < leafcount; i++)
|
|
{
|
|
if (bsp->decpvs[list[i]>>3] & (1<<(list[i]&7)))
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
void BSP_SetupForPosition(bsp_t *bsp, float x, float y, float z)
|
|
{
|
|
int leafnum;
|
|
if (!bsp)
|
|
return;
|
|
|
|
leafnum = BSP_LeafNum(bsp, x, y, z);
|
|
DecompressVis(bsp->pvsofs[leafnum], bsp->decpvs, bsp->pvsbytecount);
|
|
}
|
|
|
|
const intermission_t *BSP_IntermissionSpot(bsp_t *bsp)
|
|
{
|
|
int spotnum;
|
|
if (bsp)
|
|
{
|
|
if (bsp->numintermissionspots>0)
|
|
{
|
|
spotnum = rand()%bsp->numintermissionspots;
|
|
return &bsp->intermissionspot[spotnum];
|
|
}
|
|
}
|
|
return &nullintermissionspot;
|
|
}
|
|
|