--- /dev/null
+/* -------------------------------------------------------------------------------
+
+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
+
+int 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
+*/
+
+qboolean PlaneEqual( plane_t *p, vec3_t normal, vec_t dist )
+{
+ float ne, de;
+
+
+ /* get local copies */
+ ne = normalEpsilon;
+ de = distanceEpsilon;
+
+ /* compare */
+ // We check equality of each component since we're using '<', not '<='
+ // (the epsilons may be zero). We want to use '<' intead of '<=' to be
+ // consistent with the true meaning of "epsilon", and also because other
+ // parts of the code uses this inequality.
+ if ((p->dist == dist || fabs(p->dist - dist) < de) &&
+ (p->normal[0] == normal[0] || fabs(p->normal[0] - normal[0]) < ne) &&
+ (p->normal[1] == normal[1] || fabs(p->normal[1] - normal[1]) < ne) &&
+ (p->normal[2] == normal[2] || 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 - mapplanes + 1;
+}
+
+/*
+================
+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
+ AUTOEXPAND_BY_REALLOC(mapplanes, nummapplanes+1, allocatedmapplanes, 1024);
+
+ 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.
+Returns qtrue if and only if the normal was adjusted.
+*/
+
+qboolean SnapNormal( vec3_t normal )
+{
+#if Q3MAP2_EXPERIMENTAL_SNAP_NORMAL_FIX
+ int i;
+ qboolean adjusted = qfalse;
+
+ // A change from the original SnapNormal() is that we snap each
+ // component that's close to 0. So for example if a normal is
+ // (0.707, 0.707, 0.0000001), it will get snapped to lie perfectly in the
+ // XY plane (its Z component will be set to 0 and its length will be
+ // normalized). The original SnapNormal() didn't snap such vectors - it
+ // only snapped vectors that were near a perfect axis.
+
+ for (i = 0; i < 3; i++)
+ {
+ if (normal[i] != 0.0 && -normalEpsilon < normal[i] && normal[i] < normalEpsilon)
+ {
+ normal[i] = 0.0;
+ adjusted = qtrue;
+ }
+ }
+
+ if (adjusted)
+ {
+ VectorNormalize(normal, normal);
+ return qtrue;
+ }
+ return qfalse;
+#else
+ int i;
+
+ // I would suggest that you uncomment the following code and look at the
+ // results:
+
+ /*
+ Sys_Printf("normalEpsilon is %f\n", normalEpsilon);
+ for (i = 0;; i++)
+ {
+ normal[0] = 1.0;
+ normal[1] = 0.0;
+ normal[2] = i * 0.000001;
+ VectorNormalize(normal, normal);
+ if (1.0 - normal[0] >= normalEpsilon) {
+ Sys_Printf("(%f %f %f)\n", normal[0], normal[1], normal[2]);
+ Error("SnapNormal: test completed");
+ }
+ }
+ */
+
+ // When the normalEpsilon is 0.00001, the loop will break out when normal is
+ // (0.999990 0.000000 0.004469). In other words, this is the vector closest
+ // to axial that will NOT be snapped. Anything closer will be snaped. Now,
+ // 0.004469 is close to 1/225. The length of a circular quarter-arc of radius
+ // 1 is PI/2, or about 1.57. And 0.004469/1.57 is about 0.0028, or about
+ // 1/350. Expressed a different way, 1/350 is also about 0.26/90.
+ // This means is that a normal with an angle that is within 1/4 of a degree
+ // from axial will be "snapped". My belief is that the person who wrote the
+ // code below did not intend it this way. I think the person intended that
+ // the epsilon be measured against the vector components close to 0, not 1.0.
+ // I think the logic should be: if 2 of the normal components are within
+ // epsilon of 0, then the vector can be snapped to be perfectly axial.
+ // We may consider adjusting the epsilon to a larger value when we make this
+ // code fix.
+
+ for( i = 0; i < 3; i++ )
+ {
+ if( fabs( normal[ i ] - 1 ) < normalEpsilon )
+ {
+ VectorClear( normal );
+ normal[ i ] = 1;
+ return qtrue;
+ }
+ if( fabs( normal[ i ] - -1 ) < normalEpsilon )
+ {
+ VectorClear( normal );
+ normal[ i ] = -1;
+ return qtrue;
+ }
+ }
+ return qfalse;
+#endif
+}
+
+
+
+/*
+SnapPlane()
+snaps a plane to normal/distance epsilons
+*/
+
+void SnapPlane( vec3_t normal, vec_t *dist, vec3_t center )
+{
+// 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 );
+
+ // TODO: Rambetter has some serious comments here as well. First off,
+ // in the case where a normal is non-axial, there is nothing special
+ // about integer distances. I would think that snapping a distance might
+ // make sense for axial normals, but I'm not so sure about snapping
+ // non-axial normals. A shift by 0.01 in a plane, multiplied by a clipping
+ // against another plane that is 5 degrees off, and we introduce 0.1 error
+ // easily. A 0.1 error in a vertex is where problems start to happen, such
+ // as disappearing triangles.
+
+ // Second, assuming we have snapped the normal above, let's say that the
+ // plane we just snapped was defined for some points that are actually
+ // quite far away from normal * dist. Well, snapping the normal in this
+ // case means that we've just moved those points by potentially many units!
+ // Therefore, if we are going to snap the normal, we need to know the
+ // points we're snapping for so that the plane snaps with those points in
+ // mind (points remain close to the plane).
+
+ // I would like to know exactly which problems SnapPlane() is trying to
+ // solve so that we can better engineer it (I'm not saying that SnapPlane()
+ // should be removed altogether). Fix all this snapping code at some point!
+
+ if( fabs( *dist - Q_rint( *dist ) ) < distanceEpsilon )
+ *dist = Q_rint( *dist );
+}
+
+/*
+SnapPlaneImproved()
+snaps a plane to normal/distance epsilons, improved code
+*/
+void SnapPlaneImproved(vec3_t normal, vec_t *dist, int numPoints, const vec3_t *points)
+{
+ int i;
+ vec3_t center;
+ vec_t distNearestInt;
+
+ if (SnapNormal(normal))
+ {
+ if (numPoints > 0)
+ {
+ // Adjust the dist so that the provided points don't drift away.
+ VectorClear(center);
+ for (i = 0; i < numPoints; i++)
+ {
+ VectorAdd(center, points[i], center);
+ }
+ for (i = 0; i < 3; i++) { center[i] = center[i] / numPoints; }
+ *dist = DotProduct(normal, center);
+ }
+ }
+
+ if (VectorIsOnAxis(normal))
+ {
+ // Only snap distance if the normal is an axis. Otherwise there
+ // is nothing "natural" about snapping the distance to an integer.
+ distNearestInt = Q_rint(*dist);
+ if (-distanceEpsilon < *dist - distNearestInt && *dist - distNearestInt < distanceEpsilon)
+ {
+ *dist = distNearestInt;
+ }
+ }
+}
+
+
+
+/*
+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 innormal, vec_t dist, int numPoints, vec3_t *points ) // NOTE: this has a side effect on the normal. Good or bad?
+
+#ifdef USE_HASHING
+
+{
+ int i, j, hash, h;
+ int pidx;
+ plane_t *p;
+ vec_t d;
+ vec3_t normal;
+
+ VectorCopy(innormal, normal);
+#if Q3MAP2_EXPERIMENTAL_SNAP_PLANE_FIX
+ SnapPlaneImproved(normal, &dist, numPoints, (const vec3_t *) points);
+#else
+ SnapPlane( normal, &dist );
+#endif
+ /* hash the plane */
+ 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( pidx = planehash[ h ] - 1; pidx != -1; pidx = mapplanes[pidx].hash_chain - 1 )
+ {
+ p = &mapplanes[pidx];
+
+ /* 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++ )
+ {
+ // NOTE: When dist approaches 2^16, the resolution of 32 bit floating
+ // point number is greatly decreased. The distanceEpsilon cannot be
+ // very small when world coordinates extend to 2^16. Making the
+ // dot product here in 64 bit land will not really help the situation
+ // because the error will already be carried in dist.
+ d = DotProduct( points[ j ], p->normal ) - p->dist;
+ d = fabs(d);
+ if (d != 0.0 && d >= distanceEpsilon)
+ break; // Point is too far from plane.
+ }
+
+ /* 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;
+ vec3_t normal;
+
+ VectorCopy(innormal, normal);
+#if Q3MAP2_EXPERIMENTAL_SNAP_PLANE_FIX
+ SnapPlaneImproved(normal, &dist, numPoints, (const vec3_t *) points);
+#else
+ SnapPlane( normal, &dist );
+#endif
+ for( i = 0, p = mapplanes; i < nummapplanes; i++, p++ )
+ {
+ if( !PlaneEqual( p, normal, dist ) )
+ continue;
+
+ /* ydnar: uncomment the following line for old-style plane finding */
+ //% return i;
+
+ /* ydnar: test supplied points against this plane */
+ for( j = 0; j < numPoints; j++ )
+ {
+ d = DotProduct( points[ j ], p->normal ) - p->dist;
+ if( fabs( d ) > distanceEpsilon )
+ break;
+ }
+
+ /* found a matching plane */
+ if( j >= numPoints )
+ return i;
+ // TODO: Note that the non-USE_HASHING code does not compute epsilons
+ // for the provided points. It should do that. I think this code
+ // is unmaintained because nobody sets USE_HASHING to off.
+ }
+
+ return CreateNewFloatPlane( normal, dist );
+}
+
+#endif
+
+
+
+/*
+MapPlaneFromPoints()
+takes 3 points and finds the plane they lie in
+*/
+
+int MapPlaneFromPoints( vec3_t *p )
+{
+#if Q3MAP2_EXPERIMENTAL_HIGH_PRECISION_MATH_FIXES
+ vec3_accu_t paccu[3];
+ vec3_accu_t t1, t2, normalAccu;
+ vec3_t normal;
+ vec_t dist;
+
+ VectorCopyRegularToAccu(p[0], paccu[0]);
+ VectorCopyRegularToAccu(p[1], paccu[1]);
+ VectorCopyRegularToAccu(p[2], paccu[2]);
+
+ VectorSubtractAccu(paccu[0], paccu[1], t1);
+ VectorSubtractAccu(paccu[2], paccu[1], t2);
+ CrossProductAccu(t1, t2, normalAccu);
+ VectorNormalizeAccu(normalAccu, normalAccu);
+ // TODO: A 32 bit float for the plane distance isn't enough resolution
+ // if the plane is 2^16 units away from the origin (the "epsilon" approaches
+ // 0.01 in that case).
+ dist = (vec_t) DotProductAccu(paccu[0], normalAccu);
+ VectorCopyAccuToRegular(normalAccu, normal);
+
+ return FindFloatPlane(normal, dist, 3, p);
+#else
+ 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 );
+#endif
+}
+
+
+
+/*
+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;
+
+ contentFlags |= s->contentFlags;
+ compileFlags |= s->compileFlags;
+ }
+
+ /* 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
+*/
+
+static void MergeOrigin(entity_t *ent, vec3_t origin)
+{
+ vec3_t adjustment;
+ char string[128];
+
+ /* we have not parsed the brush completely yet... */
+ GetVectorForKey( ent, "origin", ent->origin );
+
+ VectorMA(origin, -1, ent->originbrush_origin, adjustment);
+ VectorAdd(adjustment, ent->origin, ent->origin);
+ VectorCopy(origin, ent->originbrush_origin);
+
+ sprintf(string, "%f %f %f", ent->origin[0], ent->origin[1], ent->origin[2]);
+ SetKeyValue(ent, "origin", string);
+}
+
+brush_t *FinishBrush( qboolean noCollapseGroups )
+{
+ 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 )
+ {
+ vec3_t origin;
+
+ Sys_Printf( "Entity %i, Brush %i: origin brush detected\n",
+ mapEnt->mapEntityNum, entitySourceBrushes );
+
+ 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);
+
+ MergeOrigin(&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 */
+ if(!noCollapseGroups)
+ 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 = 0;
+ 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, qboolean noCollapseGroups )
+{
+ 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(noCollapseGroups);
+}
+
+
+
+/*
+MoveBrushesToWorld()
+takes all of the brushes from the current entity and
+adds them to the world's brush list
+(used by func_group)
+*/
+
+void AdjustBrushesForOrigin( entity_t *ent );
+void MoveBrushesToWorld( entity_t *ent )
+{
+ brush_t *b, *next;
+ parseMesh_t *pm;
+
+ /* we need to undo the common/origin adjustment, and instead shift them by the entity key origin */
+ VectorScale(ent->origin, -1, ent->originbrush_origin);
+ AdjustBrushesForOrigin(ent);
+ VectorClear(ent->originbrush_origin);
+
+ /* 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->originbrush_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->originbrush_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, qboolean noCollapseGroups )
+{
+ epair_t *ep;
+ const char *classname, *value;
+ float lightmapScale, shadeAngle;
+ int lightmapSampleSize;
+ 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, noCollapseGroups );
+ }
+ 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, noCollapseGroups );
+ }
+ 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 );
+
+ /* vortex: added _ls key (short name of lightmapscale) */
+ /* ydnar: get lightmap scaling value for this entity */
+ lightmapScale = 0.0f;
+ if( strcmp( "", ValueForKey( mapEnt, "lightmapscale" ) ) ||
+ strcmp( "", ValueForKey( mapEnt, "_lightmapscale" ) ) ||
+ strcmp( "", ValueForKey( mapEnt, "_ls" ) ) )
+ {
+ /* get lightmap scale from entity */
+ lightmapScale = FloatForKey( mapEnt, "lightmapscale" );
+ if( lightmapScale <= 0.0f )
+ lightmapScale = FloatForKey( mapEnt, "_lightmapscale" );
+ if( lightmapScale <= 0.0f )
+ lightmapScale = FloatForKey( mapEnt, "_ls" );
+ if( lightmapScale < 0.0f )
+ lightmapScale = 0.0f;
+ if( lightmapScale > 0.0f )
+ Sys_Printf( "Entity %d (%s) has lightmap scale of %.4f\n", mapEnt->mapEntityNum, classname, lightmapScale );
+ }
+
+ /* ydnar: get cel shader :) for this entity */
+ value = ValueForKey( mapEnt, "_celshader" );
+ if( value[ 0 ] == '\0' )
+ value = ValueForKey( &entities[ 0 ], "_celshader" );
+ if( value[ 0 ] != '\0' )
+ {
+ if(strcmp(value, "none"))
+ {
+ 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;
+ }
+ }
+ else
+ celShader = (*globalCelShader ? ShaderInfoForShader(globalCelShader) : NULL);
+
+ /* jal : entity based _shadeangle */
+ shadeAngle = 0.0f;
+ if ( strcmp( "", ValueForKey( mapEnt, "_shadeangle" ) ) )
+ shadeAngle = FloatForKey( mapEnt, "_shadeangle" );
+ /* vortex' aliases */
+ else if ( strcmp( "", ValueForKey( mapEnt, "_smoothnormals" ) ) )
+ shadeAngle = FloatForKey( mapEnt, "_smoothnormals" );
+ else if ( strcmp( "", ValueForKey( mapEnt, "_sn" ) ) )
+ shadeAngle = FloatForKey( mapEnt, "_sn" );
+ else if ( strcmp( "", ValueForKey( mapEnt, "_smooth" ) ) )
+ shadeAngle = FloatForKey( mapEnt, "_smooth" );
+
+ if( shadeAngle < 0.0f )
+ shadeAngle = 0.0f;
+
+ if( shadeAngle > 0.0f )
+ Sys_Printf( "Entity %d (%s) has shading angle of %.4f\n", mapEnt->mapEntityNum, classname, shadeAngle );
+
+ /* jal : entity based _samplesize */
+ lightmapSampleSize = 0;
+ if ( strcmp( "", ValueForKey( mapEnt, "_lightmapsamplesize" ) ) )
+ lightmapSampleSize = IntForKey( mapEnt, "_lightmapsamplesize" );
+ else if ( strcmp( "", ValueForKey( mapEnt, "_samplesize" ) ) )
+ lightmapSampleSize = IntForKey( mapEnt, "_samplesize" );
+
+ if( lightmapSampleSize < 0 )
+ lightmapSampleSize = 0;
+
+ if( lightmapSampleSize > 0 )
+ Sys_Printf( "Entity %d (%s) has lightmap sample size of %d\n", mapEnt->mapEntityNum, classname, lightmapSampleSize );
+
+ /* 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->lightmapSampleSize = lightmapSampleSize;
+ brush->lightmapScale = lightmapScale;
+ brush->celShader = celShader;
+ brush->shadeAngleDegrees = shadeAngle;
+ }
+
+ for( patch = mapEnt->patches; patch != NULL; patch = patch->next )
+ {
+ patch->entityNum = mapEnt->mapEntityNum;
+ patch->castShadows = castShadows;
+ patch->recvShadows = recvShadows;
+ patch->lightmapSampleSize = lightmapSampleSize;
+ 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->originbrush_origin[ 0 ] || mapEnt->originbrush_origin[ 1 ] || mapEnt->originbrush_origin[ 2 ] )
+ AdjustBrushesForOrigin( mapEnt );
+
+ /* group_info entities are just for editor grouping (fixme: leak!) */
+ if( !noCollapseGroups && !Q_stricmp( "group_info", classname ) )
+ {
+ numEntities--;
+ return qtrue;
+ }
+
+ /* group entities are just for editor convenience, toss all brushes into worldspawn */
+ if( !noCollapseGroups && 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, qboolean noCollapseGroups )
+{
+ FILE *file;
+ brush_t *b;
+ int oldNumEntities = 0, 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, noCollapseGroups ) );
+
+ /* 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 );
+ }
+}