/** * @file map.c * @brief */ /* Copyright (C) 1997-2001 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 "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 ============================================================================= */ /** * @brief */ static 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; } /** * @brief */ #define NORMAL_EPSILON 0.00001 #define DIST_EPSILON 0.01 static 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; } /** * @brief */ static 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; } /** * @brief */ static 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 (%i)", nummapplanes+2); 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; } /** * @brief */ static 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; } } } /** * @brief */ static void SnapPlane (vec3_t normal, vec_t *dist) { SnapVector (normal); if (fabs(*dist - Q_rint(*dist)) < DIST_EPSILON) *dist = Q_rint(*dist); } /** * @brief */ #ifndef USE_HASHING extern 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 extern 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 /** * @brief */ static 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); } /*==================================================================== */ /** * @brief */ static 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 (f: %i, %i)\n" , b->entitynum, b->brushnum, s->contents, contents); break; } } /* if any side is translucent, mark the contents */ /* and change solid to window */ if (trans & (SURF_TRANS33|SURF_TRANS66|SURF_ALPHATEST)) { contents |= CONTENTS_TRANSLUCENT; if (contents & CONTENTS_SOLID) { contents &= ~CONTENTS_SOLID; contents |= CONTENTS_WINDOW; } } return contents; } /*============================================================================ */ /** * @brief Adds any additional planes necessary to allow the brush to be expanded * against axial bounding boxes */ static 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 (%i)", nummapbrushsides); 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 = qtrue; 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 (%i)", nummapbrushsides); 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 = qtrue; c_edgebevels++; b->numsides++; } } } } } /** * @brief makes basewindigs for sides and mins / maxs for the brush */ static 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 = qtrue; 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 qtrue; } /** * @brief */ static 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]; qboolean phongShading; if (nummapbrushes == MAX_MAP_BRUSHES) Error("nummapbrushes == MAX_MAP_BRUSHES (%i)", nummapbrushes); b = &mapbrushes[nummapbrushes]; b->original_sides = &brushsides[nummapbrushsides]; b->entitynum = num_entities-1; b->brushnum = nummapbrushes - mapent->firstbrush; b->optimizedDetail = qfalse; b->isTerrain = (!strcmp("func_group", ValueForKey(&entities[b->entitynum], "classname")) && strlen(ValueForKey(&entities[b->entitynum], "terrain")) > 0); b->isGenSurf = (!strcmp("func_group", ValueForKey(&entities[b->entitynum], "classname")) && strlen(ValueForKey(&entities[b->entitynum], "gensurf")) > 0); phongShading = (!strcmp("func_group", ValueForKey(&entities[b->entitynum], "classname")) && strlen(ValueForKey(&entities[b->entitynum], "phongshading")) > 0); #if 1 if (b->isTerrain) Sys_Printf("Brush number %i in entity number %i has terrain flag set.\n", b->brushnum, b->entitynum); if (phongShading) Sys_Printf("Brush number %i in entity number %i has phong shading flag set.\n", b->brushnum, b->entitynum); #endif do { if (!GetToken(qtrue)) break; if (!strcmp(token, "}") ) break; if (nummapbrushsides == MAX_MAP_BRUSHSIDES) Error("nummapbrushsides == MAX_MAP_BRUSHSIDES (%i)", nummapbrushsides); side = &brushsides[nummapbrushsides]; /* read the three point plane definition */ for (i = 0; i < 3; i++) { if (i != 0) GetToken(qtrue); if (strcmp(token, "(") ) Error("parsing brush"); for (j = 0; j < 3; j++) { GetToken(qfalse); planepts[i][j] = atoi(token); } GetToken(qfalse); if (strcmp(token, ")") ) Error("parsing brush"); } /* read the texturedef */ GetToken(qfalse); strcpy(td.name, token); GetToken(qfalse); td.shift[0] = atoi(token); GetToken(qfalse); td.shift[1] = atoi(token); GetToken(qfalse); td.rotate = atoi(token); GetToken(qfalse); td.scale[0] = atof(token); GetToken(qfalse); 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(qfalse); side->contents = atoi(token); GetToken(qfalse); side->surf = td.flags = atoi(token); GetToken(qfalse); td.value = atoi(token); } /* translucent objects are automatically classified as detail */ if (side->surf & (SURF_TRANS33|SURF_TRANS66|SURF_ALPHATEST) ) side->contents |= CONTENTS_DETAIL; if (fulldetail) side->contents &= ~CONTENTS_DETAIL; if (!(side->contents & (LAST_VISIBLE_CONTENTS-1) ) ) 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, b->isTerrain); /* 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_SLIME | CONTENTS_WATER))) { b->numsides = 0; return; } /* Knightmare- check if this is an optimized detail brush (has caulk faces) * also exclude trans brushes and terrain */ if ((b->contents & CONTENTS_DETAIL) && (b->contents & CONTENTS_SOLID) && !b->isTerrain && !b->isGenSurf) for (i = 0; i < b->numsides; i++) { /* nodraw/caulk faces */ if ((b->original_sides[i].surf & SURF_NODRAW) && !(b->original_sides[i].surf & SURF_SKIP)) { b->optimizedDetail = qtrue; Sys_Printf("Entity %i, Brush %i: optimized detail\n", b->entitynum, b->brushnum); break; } } /* Knightmare- special handling for terrain brushes */ if (b->isTerrain || b->isGenSurf || phongShading) for (i = 0; i < b->numsides; i++) { s2 = &b->original_sides[i]; /* set ArghRad phong shading value (because EasyGen/GTKGenSurf doesn't allow this) */ if (!(b->original_sides[i].surf & SURF_NODRAW) && (b->original_sides[i].surf & SURF_SKIP)) { texinfo[s2->texinfo].value = 777 + b->entitynum; /* lucky 7's */ texinfo[s2->texinfo].flags &= ~SURF_LIGHT; /* must not be light-emitting */ } } /* create windings for sides and bounds for brush */ MakeBrushWindings (b); #if 0 /* brushes that will not be visible at all will never be */ /* used as bsp splitters */ if (b->contents & (CONTENTS_ACTORCLIP|CONTENTS_MONSTERCLIP)) { c_clipbrushes++; for (i = 0; i < b->numsides; i++) b->original_sides[i].texinfo = TEXINFO_NODE; } #endif /* 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++; } /** * @brief Takes all of the brushes from the current entity and adds them to the world's brush list. * @note Used by func_group and func_areaportal */ static 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; } /** * @brief */ static 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(qtrue)) return qfalse; if (strcmp(token, "{") ) Error("ParseMapEntity: { not found"); if (num_entities == MAX_MAP_ENTITIES) Error("num_entities == MAX_MAP_ENTITIES (%i)", num_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(qtrue)) Error("ParseMapEntity: EOF without closing brace"); if (!strcmp(token, "}") ) break; if (!strcmp(token, "{") ) ParseBrush(mapent); else { e = ParseEpair(); e->next = mapent->epairs; mapent->epairs = e; } } while (qtrue); 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, qfalse); } 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; num_entities--; return qtrue; } #if 0 /* 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 qtrue; } #endif return qtrue; } /*#define TESTEXPANDBRUSHES*/ #ifdef TESTEXPANDBRUSHES /** * @brief Expands all the brush planes and saves a new map out */ static 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"); } #endif /* TESTEXPANDBRUSHES */ /** * @brief */ extern 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); } for (i = 0; i < nummapbrushes; i++) mapbrushes[i].finished = qfalse; /* save a copy of the brushes */ memcpy(mapbrushes + nummapbrushes, mapbrushes, sizeof(mapbrush_t)*nummapbrushes); 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]); #ifdef TESTEXPANDBRUSHES TestExpandBrushes(); #endif }