/* 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; }