/* =========================================================================== 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 ; ihash_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 ; inumsides ; 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 ; inumsides ; i++) { s = buildBrush->sides + i; w = s->winding; if (!w) continue; for (j=0 ; jnumpoints ; 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 ; lnumpoints ; 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 ; inumsides ; 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 ; inumsides ; 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"); }