mirror of
https://github.com/UberGames/GtkRadiant.git
synced 2024-11-29 23:22:23 +00:00
1838 lines
46 KiB
C
1838 lines
46 KiB
C
/* -------------------------------------------------------------------------------
|
|
|
|
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
|
|
*/
|
|
|
|
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;
|
|
}
|
|
|
|
/*
|
|
================
|
|
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.
|
|
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 ){
|
|
// 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 normal, vec_t dist, int numPoints, vec3_t *points )
|
|
|
|
#ifdef USE_HASHING
|
|
|
|
{
|
|
int i, j, hash, h;
|
|
plane_t *p;
|
|
vec_t d;
|
|
|
|
|
|
#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 ( 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++ )
|
|
{
|
|
// 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 ], normal ) - 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;
|
|
|
|
#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 ) ) {
|
|
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;
|
|
}
|
|
}
|
|
|
|
/* 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 );
|
|
}
|
|
}
|
|
}
|