/* ------------------------------------------------------------------------------- Copyright (C) 1999-2007 id Software, Inc. and contributors. For a list of contributors, see the accompanying CONTRIBUTORS file. This file is part of GtkRadiant. GtkRadiant 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. GtkRadiant 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 GtkRadiant; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA ---------------------------------------------------------------------------------- This code has been altered significantly from its original form, to support several games based on the Quake III Arena engine, in the form of "Q3Map2." ------------------------------------------------------------------------------- */ /* marker */ #define MAP_C /* dependencies */ #include "q3map2.h" /* FIXME: remove these vars */ /* undefine to make plane finding use linear sort (note: really slow) */ #define USE_HASHING #define PLANE_HASHES 8192 plane_t *planehash[ PLANE_HASHES ]; int c_boxbevels; int c_edgebevels; int c_areaportals; int c_detail; int c_structural; /* PlaneEqual() ydnar: replaced with variable epsilon for djbob */ #define NORMAL_EPSILON 0.00001 #define DIST_EPSILON 0.01 qboolean PlaneEqual( plane_t *p, vec3_t normal, vec_t dist ) { float ne, de; /* get local copies */ ne = normalEpsilon; de = distanceEpsilon; /* compare */ if( fabs( p->dist - dist ) <= de && fabs( p->normal[ 0 ] - normal[ 0 ] ) <= ne && fabs( p->normal[ 1 ] - normal[ 1 ] ) <= ne && fabs( p->normal[ 2 ] - normal[ 2 ] ) <= ne ) return qtrue; /* different */ return qfalse; } /* AddPlaneToHash() */ void AddPlaneToHash( plane_t *p ) { int hash; hash = (PLANE_HASHES - 1) & (int) fabs( p->dist ); 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) { Sys_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; } /* SnapNormal() snaps a near-axial normal vector */ void SnapNormal( vec3_t normal ) { int i; for( i = 0; i < 3; i++ ) { if( fabs( normal[ i ] - 1 ) < normalEpsilon ) { VectorClear( normal ); normal[ i ] = 1; break; } if( fabs( normal[ i ] - -1 ) < normalEpsilon ) { VectorClear( normal ); normal[ i ] = -1; break; } } } /* SnapPlane() snaps a plane to normal/distance epsilons */ void SnapPlane( vec3_t normal, vec_t *dist ) { // SnapPlane disabled by LordHavoc because it often messes up collision // brushes made from triangles of embedded models, and it has little effect // on anything else (axial planes are usually derived from snapped points) /* SnapPlane reenabled by namespace because of multiple reports of q3map2-crashes which were triggered by this patch. */ SnapNormal( normal ); if( fabs( *dist - Q_rint( *dist ) ) < distanceEpsilon ) *dist = Q_rint( *dist ); } /* FindFloatPlane() ydnar: changed to allow a number of test points to be supplied that must be within an epsilon distance of the plane */ int FindFloatPlane( vec3_t normal, vec_t dist, int numPoints, vec3_t *points ) #ifdef USE_HASHING { int i, j, hash, h; plane_t *p; vec_t d; /* hash the plane */ SnapPlane( normal, &dist ); hash = (PLANE_HASHES - 1) & (int) fabs( dist ); /* search the border bins as well */ for( i = -1; i <= 1; i++ ) { h = (hash + i) & (PLANE_HASHES - 1); for( p = planehash[ h ]; p != NULL; p = p->hash_chain ) { /* do standard plane compare */ if( !PlaneEqual( p, normal, dist ) ) continue; /* ydnar: uncomment the following line for old-style plane finding */ //% return p - mapplanes; /* ydnar: test supplied points against this plane */ for( j = 0; j < numPoints; j++ ) { d = DotProduct( points[ j ], normal ) - dist; if( fabs( d ) > distanceEpsilon ) break; } /* found a matching plane */ if( j >= numPoints ) return p - mapplanes; } } /* none found, so create a new one */ return CreateNewFloatPlane( normal, dist ); } #else { 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 ); } #endif /* MapPlaneFromPoints() takes 3 points and finds the plane they lie in */ int MapPlaneFromPoints( vec3_t *p ) { vec3_t t1, t2, normal; vec_t dist; /* calc plane normal */ VectorSubtract( p[ 0 ], p[ 1 ], t1 ); VectorSubtract( p[ 2 ], p[ 1 ], t2 ); CrossProduct( t1, t2, normal ); VectorNormalize( normal, normal ); /* calc plane distance */ dist = DotProduct( p[ 0 ], normal ); /* store the plane */ return FindFloatPlane( normal, dist, 3, p ); } /* SetBrushContents() the content flags and compile flags on all sides of a brush should be the same */ void SetBrushContents( brush_t *b ) { int contentFlags, compileFlags; side_t *s; int i; qboolean mixed; /* get initial compile flags from first side */ s = &b->sides[ 0 ]; contentFlags = s->contentFlags; compileFlags = s->compileFlags; b->contentShader = s->shaderInfo; mixed = qfalse; /* get the content/compile flags for every side in the brush */ for( i = 1; i < b->numsides; i++, s++ ) { s = &b->sides[ i ]; if( s->shaderInfo == NULL ) continue; if( s->contentFlags != contentFlags || s->compileFlags != compileFlags ) mixed = qtrue; } /* ydnar: getting rid of this stupid warning */ //% if( mixed ) //% Sys_FPrintf( SYS_VRB,"Entity %i, Brush %i: mixed face contentFlags\n", b->entitynum, b->brushnum ); /* check for detail & structural */ if( (compileFlags & C_DETAIL) && (compileFlags & C_STRUCTURAL) ) { xml_Select( "Mixed detail and structural (defaulting to structural)", mapEnt->mapEntityNum, entitySourceBrushes, qfalse ); compileFlags &= ~C_DETAIL; } /* the fulldetail flag will cause detail brushes to be treated like normal brushes */ if( fulldetail ) compileFlags &= ~C_DETAIL; /* all translucent brushes that aren't specifically made structural will be detail */ if( (compileFlags & C_TRANSLUCENT) && !(compileFlags & C_STRUCTURAL) ) compileFlags |= C_DETAIL; /* detail? */ if( compileFlags & C_DETAIL ) { c_detail++; b->detail = qtrue; } else { c_structural++; b->detail = qfalse; } /* opaque? */ if( compileFlags & C_TRANSLUCENT ) b->opaque = qfalse; else b->opaque = qtrue; /* areaportal? */ if( compileFlags & C_AREAPORTAL ) c_areaportals++; /* set brush flags */ b->contentFlags = contentFlags; b->compileFlags = compileFlags; } /* AddBrushBevels() adds any additional planes necessary to allow the brush being built to be expanded against axial bounding boxes ydnar 2003-01-20: added mrelusive fixes */ void AddBrushBevels( void ) { int axis, dir; int i, j, k, l, order; side_t sidetemp; side_t *s, *s2; winding_t *w, *w2; vec3_t normal; float dist; vec3_t vec, vec2; float d, minBack; // // 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++ ) { /* ydnar: testing disabling of mre code */ #if 0 if ( dir > 0 ) { if ( mapplanes[s->planenum].normal[axis] >= 0.9999f ) { break; } } else { if ( mapplanes[s->planenum].normal[axis] <= -0.9999f ) { break; } } #else if( (dir > 0 && mapplanes[ s->planenum ].normal[ axis ] == 1.0f ) || (dir < 0 && mapplanes[ s->planenum ].normal[ axis ] == -1.0f) ) break; #endif } if ( i == buildBrush->numsides ) { // add a new side if ( buildBrush->numsides == MAX_BUILD_SIDES ) { xml_Select( "MAX_BUILD_SIDES", buildBrush->entityNum, buildBrush->brushNum, qtrue); } memset( s, 0, sizeof( *s ) ); buildBrush->numsides++; VectorClear (normal); normal[axis] = dir; if( dir == 1 ) { /* ydnar: adding bevel plane snapping for fewer bsp planes */ if( bevelSnap > 0 ) dist = floor( buildBrush->maxs[ axis ] / bevelSnap ) * bevelSnap; else dist = buildBrush->maxs[ axis ]; } else { /* ydnar: adding bevel plane snapping for fewer bsp planes */ if( bevelSnap > 0 ) dist = -ceil( buildBrush->mins[ axis ] / bevelSnap ) * bevelSnap; else dist = -buildBrush->mins[ axis ]; } s->planenum = FindFloatPlane( normal, dist, 0, NULL ); s->contentFlags = buildBrush->sides[ 0 ].contentFlags; 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 } // test the non-axial plane edges for ( i = 6; i < buildBrush->numsides; i++ ) { s = buildBrush->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.5f ) { continue; } SnapNormal( vec ); for ( k = 0; k < 3; k++ ) { if ( vec[k] == -1.0f || vec[k] == 1.0f || (vec[k] == 0.0f && vec[(k+1)%3] == 0.0f) ) { break; // axial } } if ( k != 3 ) { continue; // only test non-axial edges } /* debug code */ //% Sys_Printf( "-------------\n" ); // 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.5f ) { 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; } minBack = 0.0f; for ( l = 0; l < w2->numpoints; l++ ) { d = DotProduct( w2->p[l], normal ) - dist; if ( d > 0.1f ) { break; // point in front } if ( d < minBack ) { minBack = d; } } // if some point was at the front if ( l != w2->numpoints ) { break; } // if no points at the back then the winding is on the bevel plane if ( minBack > -0.1f ) { //% Sys_Printf( "On bevel plane\n" ); break; } } if ( k != buildBrush->numsides ) { continue; // wasn't part of the outer hull } /* debug code */ //% Sys_Printf( "n = %f %f %f\n", normal[ 0 ], normal[ 1 ], normal[ 2 ] ); // add this plane if( buildBrush->numsides == MAX_BUILD_SIDES ) { xml_Select( "MAX_BUILD_SIDES", buildBrush->entityNum, buildBrush->brushNum, qtrue); } s2 = &buildBrush->sides[buildBrush->numsides]; buildBrush->numsides++; memset( s2, 0, sizeof( *s2 ) ); s2->planenum = FindFloatPlane( normal, dist, 1, &w->p[ j ] ); s2->contentFlags = buildBrush->sides[0].contentFlags; s2->bevel = qtrue; c_edgebevels++; } } } } } /* FinishBrush() produces a final brush based on the buildBrush->sides array and links it to the current entity */ brush_t *FinishBrush( void ) { brush_t *b; /* create windings for sides and bounds for brush */ if ( !CreateBrushWindings( buildBrush ) ) return NULL; /* 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->compileFlags & C_ORIGIN ) { char string[ 32 ]; vec3_t origin; if( numEntities == 1 ) { Sys_Printf( "Entity %i, Brush %i: origin brushes not allowed in world\n", mapEnt->mapEntityNum, 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[ numEntities - 1 ], "origin", string); VectorCopy( origin, entities[ numEntities - 1 ].origin); /* don't keep this brush */ return NULL; } /* determine if the brush is an area portal */ if( buildBrush->compileFlags & C_AREAPORTAL ) { if( numEntities != 1 ) { Sys_Printf ("Entity %i, Brush %i: areaportals only allowed in world\n", numEntities - 1, entitySourceBrushes ); return NULL; } } /* add bevel planes */ AddBrushBevels(); /* keep it */ b = CopyBrush( buildBrush ); /* set map entity and brush numbering */ b->entityNum = mapEnt->mapEntityNum; b->brushNum = entitySourceBrushes; /* set original */ b->original = b; /* link opaque brushes to head of list, translucent brushes to end */ if( b->opaque || mapEnt->lastBrush == NULL ) { b->next = mapEnt->brushes; mapEnt->brushes = b; if( mapEnt->lastBrush == NULL ) mapEnt->lastBrush = b; } else { b->next = NULL; mapEnt->lastBrush->next = b; mapEnt->lastBrush = b; } /* link colorMod volume brushes to the entity directly */ if( b->contentShader != NULL && b->contentShader->colorMod != NULL && b->contentShader->colorMod->type == CM_VOLUME ) { b->nextColorModBrush = mapEnt->colorModBrushes; mapEnt->colorModBrushes = b; } /* return to sender */ return b; } /* TextureAxisFromPlane() determines best orthagonal axis to project a texture onto a wall (must be identical in radiant!) */ 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 + 0.0001f ) /* ydnar: bug 637 fix, suggested by jmonroe */ { 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() 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 */ static void ParseRawBrush( qboolean onlyLights ) { side_t *side; vec3_t planePoints[ 3 ]; int planenum; shaderInfo_t *si; vec_t shift[ 2 ]; vec_t rotate; vec_t scale[ 2 ]; char name[ MAX_QPATH ]; char shader[ MAX_QPATH ]; int flags; /* initial setup */ buildBrush->numsides = 0; buildBrush->detail = qfalse; /* bp */ if( g_bBrushPrimit == BPRIMIT_NEWBRUSHES ) MatchToken( "{" ); /* parse sides */ while( 1 ) { if( !GetToken( qtrue ) ) break; if( !strcmp( token, "}" ) ) break; /* ttimo : bp: here we may have to jump over brush epairs (only used in editor) */ if( g_bBrushPrimit == BPRIMIT_NEWBRUSHES ) { while( 1 ) { if( strcmp( token, "(" ) ) GetToken( qfalse ); else break; GetToken( qtrue ); } } UnGetToken(); /* test side count */ if( buildBrush->numsides >= MAX_BUILD_SIDES ) xml_Select( "MAX_BUILD_SIDES", buildBrush->entityNum, buildBrush->brushNum, qtrue ); /* add side */ side = &buildBrush->sides[ buildBrush->numsides ]; memset( side, 0, sizeof( *side ) ); buildBrush->numsides++; /* read the three point plane definition */ Parse1DMatrix( 3, planePoints[ 0 ] ); Parse1DMatrix( 3, planePoints[ 1 ] ); Parse1DMatrix( 3, planePoints[ 2 ] ); /* bp: read the texture matrix */ if( g_bBrushPrimit == BPRIMIT_NEWBRUSHES ) Parse2DMatrix( 2, 3, (float*) side->texMat ); /* read shader name */ GetToken( qfalse ); strcpy( name, token ); /* bp */ if( g_bBrushPrimit == BPRIMIT_OLDBRUSHES ) { GetToken( qfalse ); shift[ 0 ] = atof( token ); GetToken( qfalse ); shift[ 1 ] = atof( token ); GetToken( qfalse ); rotate = atof( token ); GetToken( qfalse ); scale[ 0 ] = atof( token ); GetToken( qfalse ); scale[ 1 ] = atof( token ); } /* set default flags and values */ sprintf( shader, "textures/%s", name ); if( onlyLights ) si = &shaderInfo[ 0 ]; else si = ShaderInfoForShader( shader ); side->shaderInfo = si; side->surfaceFlags = si->surfaceFlags; side->contentFlags = si->contentFlags; side->compileFlags = si->compileFlags; side->value = si->value; /* ydnar: gs mods: bias texture shift */ if( si->globalTexture == qfalse ) { shift[ 0 ] -= (floor( shift[ 0 ] / si->shaderWidth ) * si->shaderWidth); shift[ 1 ] -= (floor( shift[ 1 ] / si->shaderHeight ) * si->shaderHeight); } /* historically, there are 3 integer values at the end of a brushside line in a .map file. in quake 3, the only thing that mattered was the first of these three values, which was previously the content flags. and only then did a single bit matter, the detail bit. because every game has its own special flags for specifying detail, the traditionally game-specified CONTENTS_DETAIL flag was overridden for Q3Map 2.3.0 by C_DETAIL, defined in q3map2.h. the value is exactly as it was before, but is stored in compileFlags, as opposed to contentFlags, for multiple-game portability. :sigh: */ if( TokenAvailable() ) { /* get detail bit from map content flags */ GetToken( qfalse ); flags = atoi( token ); if( flags & C_DETAIL ) side->compileFlags |= C_DETAIL; /* historical */ GetToken( qfalse ); //% td.flags = atoi( token ); GetToken( qfalse ); //% td.value = atoi( token ); } /* find the plane number */ planenum = MapPlaneFromPoints( planePoints ); side->planenum = planenum; /* bp: get the texture mapping for this texturedef / plane combination */ if( g_bBrushPrimit == BPRIMIT_OLDBRUSHES ) QuakeTextureVecs( &mapplanes[ planenum ], shift, rotate, scale, side->vecs ); } /* bp */ 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( brush_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) { xml_Select( "degenerate plane", b->entityNum, b->brushNum, qfalse ); // 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 ) { xml_Select( "duplicate plane", b->entityNum, b->brushNum, qfalse ); // 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 xml_Select( "mirrored plane", b->entityNum, b->brushNum, qfalse ); return qfalse; } } } return qtrue; } /* ParseBrush() parses a brush out of a map file and sets it up */ static void ParseBrush( qboolean onlyLights ) { brush_t *b; /* parse the brush out of the map */ ParseRawBrush( onlyLights ); /* only go this far? */ if( onlyLights ) return; /* set some defaults */ buildBrush->portalareas[ 0 ] = -1; buildBrush->portalareas[ 1 ] = -1; buildBrush->entityNum = numMapEntities - 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->compileFlags & C_DETAIL) ) { //% FreeBrush( buildBrush ); return; } /* allow liquid brushes to be removed */ if( nowater && (buildBrush->compileFlags & C_LIQUID ) ) { //% FreeBrush( buildBrush ); return; } /* ydnar: allow hint brushes to be removed */ if( noHint && (buildBrush->compileFlags & C_HINT) ) { //% FreeBrush( buildBrush ); return; } /* finish the brush */ b = FinishBrush(); } /* 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 *ent ) { brush_t *b, *next; parseMesh_t *pm; /* move brushes */ for( b = ent->brushes; b != NULL; b = next ) { /* get next brush */ next = b->next; /* link opaque brushes to head of list, translucent brushes to end */ if( b->opaque || entities[ 0 ].lastBrush == NULL ) { b->next = entities[ 0 ].brushes; entities[ 0 ].brushes = b; if( entities[ 0 ].lastBrush == NULL ) entities[ 0 ].lastBrush = b; } else { b->next = NULL; entities[ 0 ].lastBrush->next = b; entities[ 0 ].lastBrush = b; } } ent->brushes = NULL; /* ydnar: move colormod brushes */ if( ent->colorModBrushes != NULL ) { for( b = ent->colorModBrushes; b->nextColorModBrush != NULL; b = b->nextColorModBrush ); b->nextColorModBrush = entities[ 0 ].colorModBrushes; entities[ 0 ].colorModBrushes = ent->colorModBrushes; ent->colorModBrushes = NULL; } /* move patches */ if( ent->patches != NULL ) { for( pm = ent->patches; pm->next != NULL; pm = pm->next ); pm->next = entities[ 0 ].patches; entities[ 0 ].patches = ent->patches; ent->patches = NULL; } } /* AdjustBrushesForOrigin() */ void AdjustBrushesForOrigin( entity_t *ent ) { int i; side_t *s; vec_t newdist; brush_t *b; parseMesh_t *p; /* walk brush list */ for( b = ent->brushes; b != NULL; b = b->next ) { /* offset brush planes */ for( i = 0; i < b->numsides; i++) { /* get brush side */ s = &b->sides[ i ]; /* offset side plane */ newdist = mapplanes[ s->planenum ].dist - DotProduct( mapplanes[ s->planenum ].normal, ent->origin ); /* find a new plane */ s->planenum = FindFloatPlane( mapplanes[ s->planenum ].normal, newdist, 0, NULL ); } /* rebuild brush windings (ydnar: just offsetting the winding above should be fine) */ CreateBrushWindings( b ); } /* walk patch list */ for( p = ent->patches; p != NULL; 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 ); } } /* SetEntityBounds() - ydnar finds the bounds of an entity's brushes (necessary for terrain-style generic metashaders) */ void SetEntityBounds( entity_t *e ) { int i; brush_t *b; parseMesh_t *p; vec3_t mins, maxs; const char *value; /* walk the entity's brushes/patches and determine bounds */ ClearBounds( mins, maxs ); for( b = e->brushes; b; b = b->next ) { AddPointToBounds( b->mins, mins, maxs ); AddPointToBounds( b->maxs, mins, maxs ); } for( p = e->patches; p; p = p->next ) { for( i = 0; i < (p->mesh.width * p->mesh.height); i++ ) AddPointToBounds( p->mesh.verts[ i ].xyz, mins, maxs ); } /* try to find explicit min/max key */ value = ValueForKey( e, "min" ); if( value[ 0 ] != '\0' ) GetVectorForKey( e, "min", mins ); value = ValueForKey( e, "max" ); if( value[ 0 ] != '\0' ) GetVectorForKey( e, "max", maxs ); /* store the bounds */ for( b = e->brushes; b; b = b->next ) { VectorCopy( mins, b->eMins ); VectorCopy( maxs, b->eMaxs ); } for( p = e->patches; p; p = p->next ) { VectorCopy( mins, p->eMins ); VectorCopy( maxs, p->eMaxs ); } } /* LoadEntityIndexMap() - ydnar based on LoadAlphaMap() from terrain.c, a little more generic */ void LoadEntityIndexMap( entity_t *e ) { int i, size, numLayers, w, h; const char *value, *indexMapFilename, *shader; char ext[ MAX_QPATH ], offset[ 4096 ], *search, *space; byte *pixels; unsigned int *pixels32; indexMap_t *im; brush_t *b; parseMesh_t *p; /* this only works with bmodel ents */ if( e->brushes == NULL && e->patches == NULL ) return; /* determine if there is an index map (support legacy "alphamap" key as well) */ value = ValueForKey( e, "_indexmap" ); if( value[ 0 ] == '\0' ) value = ValueForKey( e, "alphamap" ); if( value[ 0 ] == '\0' ) return; indexMapFilename = value; /* get number of layers (support legacy "layers" key as well) */ value = ValueForKey( e, "_layers" ); if( value[ 0 ] == '\0' ) value = ValueForKey( e, "layers" ); if( value[ 0 ] == '\0' ) { Sys_Printf( "WARNING: Entity with index/alpha map \"%s\" has missing \"_layers\" or \"layers\" key\n", indexMapFilename ); Sys_Printf( "Entity will not be textured properly. Check your keys/values.\n" ); return; } numLayers = atoi( value ); if( numLayers < 1 ) { Sys_Printf( "WARNING: Entity with index/alpha map \"%s\" has < 1 layer (%d)\n", indexMapFilename, numLayers ); Sys_Printf( "Entity will not be textured properly. Check your keys/values.\n" ); return; } /* get base shader name (support legacy "shader" key as well) */ value = ValueForKey( mapEnt, "_shader" ); if( value[ 0 ] == '\0' ) value = ValueForKey( e, "shader" ); if( value[ 0 ] == '\0' ) { Sys_Printf( "WARNING: Entity with index/alpha map \"%s\" has missing \"_shader\" or \"shader\" key\n", indexMapFilename ); Sys_Printf( "Entity will not be textured properly. Check your keys/values.\n" ); return; } shader = value; /* note it */ Sys_FPrintf( SYS_VRB, "Entity %d (%s) has shader index map \"%s\"\n", mapEnt->mapEntityNum, ValueForKey( e, "classname" ), indexMapFilename ); /* get index map file extension */ ExtractFileExtension( indexMapFilename, ext ); /* handle tga image */ if( !Q_stricmp( ext, "tga" ) ) { /* load it */ Load32BitImage( indexMapFilename, &pixels32, &w, &h ); /* convert to bytes */ size = w * h; pixels = safe_malloc( size ); for( i = 0; i < size; i++ ) { pixels[ i ] = ((pixels32[ i ] & 0xFF) * numLayers) / 256; if( pixels[ i ] >= numLayers ) pixels[ i ] = numLayers - 1; } /* free the 32 bit image */ free( pixels32 ); } else { /* load it */ Load256Image( indexMapFilename, &pixels, NULL, &w, &h ); /* debug code */ //% Sys_Printf( "-------------------------------" ); /* fix up out-of-range values */ size = w * h; for( i = 0; i < size; i++ ) { if( pixels[ i ] >= numLayers ) pixels[ i ] = numLayers - 1; /* debug code */ //% if( (i % w) == 0 ) //% Sys_Printf( "\n" ); //% Sys_Printf( "%c", pixels[ i ] + '0' ); } /* debug code */ //% Sys_Printf( "\n-------------------------------\n" ); } /* the index map must be at least 2x2 pixels */ if( w < 2 || h < 2 ) { Sys_Printf( "WARNING: Entity with index/alpha map \"%s\" is smaller than 2x2 pixels\n", indexMapFilename ); Sys_Printf( "Entity will not be textured properly. Check your keys/values.\n" ); free( pixels ); return; } /* create a new index map */ im = safe_malloc( sizeof( *im ) ); memset( im, 0, sizeof( *im ) ); /* set it up */ im->w = w; im->h = h; im->numLayers = numLayers; strcpy( im->name, indexMapFilename ); strcpy( im->shader, shader ); im->pixels = pixels; /* get height offsets */ value = ValueForKey( mapEnt, "_offsets" ); if( value[ 0 ] == '\0' ) value = ValueForKey( e, "offsets" ); if( value[ 0 ] != '\0' ) { /* value is a space-seperated set of numbers */ strcpy( offset, value ); search = offset; /* get each value */ for( i = 0; i < 256 && *search != '\0'; i++ ) { space = strstr( search, " " ); if( space != NULL ) *space = '\0'; im->offsets[ i ] = atof( search ); if( space == NULL ) break; search = space + 1; } } /* store the index map in every brush/patch in the entity */ for( b = e->brushes; b != NULL; b = b->next ) b->im = im; for( p = e->patches; p != NULL; p = p->next ) p->im = im; } /* ParseMapEntity() parses a single entity out of a map file */ static qboolean ParseMapEntity( qboolean onlyLights ) { epair_t *ep; const char *classname, *value; float lightmapScale; char shader[ MAX_QPATH ]; shaderInfo_t *celShader = NULL; brush_t *brush; parseMesh_t *patch; qboolean funcGroup; int castShadows, recvShadows; /* eof check */ if( !GetToken( qtrue ) ) return qfalse; /* conformance check */ if( strcmp( token, "{" ) ) { Sys_Printf( "WARNING: ParseEntity: { not found, found %s on line %d - last entity was at: <%4.2f, %4.2f, %4.2f>...\n" "Continuing to process map, but resulting BSP may be invalid.\n", token, scriptline, entities[ numEntities ].origin[ 0 ], entities[ numEntities ].origin[ 1 ], entities[ numEntities ].origin[ 2 ] ); return qfalse; } /* range check */ if( numEntities >= MAX_MAP_ENTITIES ) Error( "numEntities == MAX_MAP_ENTITIES" ); /* setup */ entitySourceBrushes = 0; mapEnt = &entities[ numEntities ]; numEntities++; memset( mapEnt, 0, sizeof( *mapEnt ) ); /* ydnar: true entity numbering */ mapEnt->mapEntityNum = numMapEntities; numMapEntities++; /* loop */ while( 1 ) { /* get initial token */ if( !GetToken( qtrue ) ) { Sys_Printf( "WARNING: ParseEntity: EOF without closing brace\n" "Continuing to process map, but resulting BSP may be invalid.\n" ); return qfalse; } if( !strcmp( token, "}" ) ) break; if( !strcmp( token, "{" ) ) { /* parse a brush or patch */ if( !GetToken( qtrue ) ) break; /* check */ if( !strcmp( token, "patchDef2" ) ) { numMapPatches++; ParsePatch( onlyLights ); } else if( !strcmp( token, "terrainDef" ) ) { //% ParseTerrain(); Sys_Printf( "WARNING: Terrain entity parsing not supported in this build.\n" ); /* ydnar */ } 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( onlyLights ); } 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( onlyLights ); } entitySourceBrushes++; } else { /* parse a key / value pair */ ep = ParseEPair(); /* ydnar: 2002-07-06 fixed wolf bug with empty epairs */ if( ep->key[ 0 ] != '\0' && ep->value[ 0 ] != '\0' ) { ep->next = mapEnt->epairs; mapEnt->epairs = ep; } } } /* ydnar: get classname */ classname = ValueForKey( mapEnt, "classname" ); /* ydnar: only lights? */ if( onlyLights && Q_strncasecmp( classname, "light", 5 ) ) { numEntities--; return qtrue; } /* ydnar: determine if this is a func_group */ if( !Q_stricmp( "func_group", classname ) ) funcGroup = qtrue; else funcGroup = qfalse; /* worldspawn (and func_groups) default to cast/recv shadows in worldspawn group */ if( funcGroup || mapEnt->mapEntityNum == 0 ) { //% Sys_Printf( "World: %d\n", mapEnt->mapEntityNum ); castShadows = WORLDSPAWN_CAST_SHADOWS; recvShadows = WORLDSPAWN_RECV_SHADOWS; } /* other entities don't cast any shadows, but recv worldspawn shadows */ else { //% Sys_Printf( "Entity: %d\n", mapEnt->mapEntityNum ); castShadows = ENTITY_CAST_SHADOWS; recvShadows = ENTITY_RECV_SHADOWS; } /* get explicit shadow flags */ GetEntityShadowFlags( mapEnt, NULL, &castShadows, &recvShadows ); /* ydnar: get lightmap scaling value for this entity */ if( strcmp( "", ValueForKey( mapEnt, "lightmapscale" ) ) || strcmp( "", ValueForKey( mapEnt, "_lightmapscale" ) ) ) { /* get lightmap scale from entity */ lightmapScale = FloatForKey( mapEnt, "lightmapscale" ); if( lightmapScale <= 0.0f ) lightmapScale = FloatForKey( mapEnt, "_lightmapscale" ); if( lightmapScale > 0.0f ) Sys_Printf( "Entity %d (%s) has lightmap scale of %.4f\n", mapEnt->mapEntityNum, classname, lightmapScale ); } else lightmapScale = 0.0f; /* ydnar: get cel shader :) for this entity */ value = ValueForKey( mapEnt, "_celshader" ); if( value[ 0 ] == '\0' ) value = ValueForKey( &entities[ 0 ], "_celshader" ); if( value[ 0 ] != '\0' ) { sprintf( shader, "textures/%s", value ); celShader = ShaderInfoForShader( shader ); Sys_Printf( "Entity %d (%s) has cel shader %s\n", mapEnt->mapEntityNum, classname, celShader->shader ); } else celShader = NULL; /* attach stuff to everything in the entity */ for( brush = mapEnt->brushes; brush != NULL; brush = brush->next ) { brush->entityNum = mapEnt->mapEntityNum; brush->castShadows = castShadows; brush->recvShadows = recvShadows; brush->lightmapScale = lightmapScale; brush->celShader = celShader; } for( patch = mapEnt->patches; patch != NULL; patch = patch->next ) { patch->entityNum = mapEnt->mapEntityNum; patch->castShadows = castShadows; patch->recvShadows = recvShadows; patch->lightmapScale = lightmapScale; patch->celShader = celShader; } /* ydnar: gs mods: set entity bounds */ SetEntityBounds( mapEnt ); /* ydnar: gs mods: load shader index map (equivalent to old terrain alphamap) */ LoadEntityIndexMap( mapEnt ); /* get entity origin and adjust brushes */ GetVectorForKey( mapEnt, "origin", mapEnt->origin ); if( mapEnt->origin[ 0 ] || mapEnt->origin[ 1 ] || mapEnt->origin[ 2 ] ) AdjustBrushesForOrigin( mapEnt ); /* group_info entities are just for editor grouping (fixme: leak!) */ if( !Q_stricmp( "group_info", classname ) ) { numEntities--; return qtrue; } /* group entities are just for editor convenience, toss all brushes into worldspawn */ if( funcGroup ) { MoveBrushesToWorld( mapEnt ); numEntities--; return qtrue; } /* done */ return qtrue; } /* LoadMapFile() loads a map file into a list of entities */ void LoadMapFile( char *filename, qboolean onlyLights ) { FILE *file; brush_t *b; int oldNumEntities, numMapBrushes; /* note it */ Sys_FPrintf( SYS_VRB, "--- LoadMapFile ---\n" ); Sys_Printf( "Loading %s\n", filename ); /* hack */ file = SafeOpenRead( filename ); fclose( file ); /* load the map file */ LoadScriptFile( filename, -1 ); /* setup */ if( onlyLights ) oldNumEntities = numEntities; else numEntities = 0; /* initial setup */ 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 ); /* parse the map file */ while( ParseMapEntity( onlyLights ) ); /* light loading */ if( onlyLights ) { /* emit some statistics */ Sys_FPrintf( SYS_VRB, "%9d light entities\n", numEntities - oldNumEntities ); } else { /* set map bounds */ ClearBounds( mapMins, mapMaxs ); for( b = entities[ 0 ].brushes; b; b = b->next ) { AddPointToBounds( b->mins, mapMins, mapMaxs ); AddPointToBounds( b->maxs, mapMins, mapMaxs ); } /* get brush counts */ numMapBrushes = CountBrushList( entities[ 0 ].brushes ); if( (float) c_detail / (float) numMapBrushes < 0.10f && numMapBrushes > 500 ) Sys_Printf( "WARNING: Over 90 percent structural map detected. Compile time may be adversely affected.\n" ); /* emit some statistics */ Sys_FPrintf( SYS_VRB, "%9d total world brushes\n", numMapBrushes ); Sys_FPrintf( SYS_VRB, "%9d detail brushes\n", c_detail ); Sys_FPrintf( SYS_VRB, "%9d patches\n", numMapPatches); Sys_FPrintf( SYS_VRB, "%9d boxbevels\n", c_boxbevels); Sys_FPrintf( SYS_VRB, "%9d edgebevels\n", c_edgebevels); Sys_FPrintf( SYS_VRB, "%9d entities\n", numEntities ); Sys_FPrintf( SYS_VRB, "%9d planes\n", nummapplanes); Sys_Printf( "%9d areaportals\n", c_areaportals); Sys_Printf( "Size: %5.0f, %5.0f, %5.0f to %5.0f, %5.0f, %5.0f\n", mapMins[ 0 ], mapMins[ 1 ], mapMins[ 2 ], mapMaxs[ 0 ], mapMaxs[ 1 ], mapMaxs[ 2 ]); /* write bogus map */ if( fakemap ) WriteBSPBrushMap( "fakemap.map", entities[ 0 ].brushes ); } }