Paranoia2/utils/p2csg/brush.cpp

1486 lines
36 KiB
C++

/***
*
* Copyright (c) 1996-2002, Valve LLC. All rights reserved.
*
* This product contains software technology licensed from Id
* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc.
* All Rights Reserved.
*
****/
// brush.c
#include "csg.h"
plane_t g_mapplanes[MAX_INTERNAL_MAP_PLANES];
int g_planehash[PLANE_HASHES];
int g_nummapplanes;
/*
=============================================================================
PLANE FINDING
=============================================================================
*/
/*
================
PlaneEqual
================
*/
bool PlaneEqual( const plane_t *p, const vec3_t normal, const vec3_t origin, vec_t dist )
{
vec_t t;
if( -DIR_EPSILON < ( t = normal[0] - p->normal[0] ) && t < DIR_EPSILON &&
-DIR_EPSILON < ( t = normal[1] - p->normal[1] ) && t < DIR_EPSILON &&
-DIR_EPSILON < ( t = normal[2] - p->normal[2] ) && t < DIR_EPSILON )
{
t = PlaneDiff2( origin, p );
if( -DIST_EPSILON < t && t < DIST_EPSILON )
return true;
}
return false;
}
/*
================
AddPlaneToHash
================
*/
void AddPlaneToHash( plane_t *p )
{
int hash;
hash = (PLANE_HASHES - 1) & (int)fabs( p->dist );
p->hash_chain = g_planehash[hash];
g_planehash[hash] = p - g_mapplanes + 1;
}
/*
================
CreateNewFloatPlane
================
*/
int CreateNewFloatPlane( const vec3_t srcnormal, const vec3_t origin )
{
plane_t *p0, *p1, temp;
vec3_t normal;
vec_t dist;
int type;
if( VectorLength( srcnormal ) < 0.5 )
return -1;
// create a new plane
if(( g_nummapplanes + 2 ) > MAX_INTERNAL_MAP_PLANES )
COM_FatalError( "MAX_INTERNAL_MAP_PLANES limit exceeded\n" );
p0 = &g_mapplanes[g_nummapplanes+0];
p1 = &g_mapplanes[g_nummapplanes+1];
// snap plane normal
VectorCopy( srcnormal, normal );
type = SnapNormal( normal );
// snap plane distance
dist = DotProduct( origin, normal );
// only snap distance if the normal is an axis. Otherwise there
// is nothing "natural" about snapping the distance to an integer.
if( VectorIsOnAxis( normal ) && fabs( dist - Q_rint( dist )) < DIST_EPSILON )
dist = Q_rint( dist ); // catch -0.0
VectorCopy( origin, p0->origin );
VectorCopy( normal, p0->normal );
VectorCopy( origin, p1->origin );
VectorNegate( normal, p1->normal );
p0->dist = dist;
p1->dist = -dist;
p0->type = type;
p1->type = type;
g_nummapplanes += 2;
// always put axial planes facing positive first
if( normal[type % 3] < 0 )
{
// flip order
temp = *p0;
*p0 = *p1;
*p1 = temp;
AddPlaneToHash( p0 );
AddPlaneToHash( p1 );
return g_nummapplanes - 1;
}
AddPlaneToHash( p0 );
AddPlaneToHash( p1 );
return g_nummapplanes - 2;
}
/*
=============
FindFloatPlane
=============
*/
int FindFloatPlane( const vec3_t normal, const vec3_t origin )
{
int i, hash, h;
vec_t dist;
plane_t *p;
dist = DotProduct( origin, normal );
hash = (PLANE_HASHES - 1) & (int)fabs( dist );
ThreadLock();
// search the border bins as well
for( i = -1; i <= 1; i++ )
{
h = (hash + i) & (PLANE_HASHES - 1);
for( int pidx = g_planehash[h] - 1; pidx != -1; pidx = g_mapplanes[pidx].hash_chain - 1 )
{
p = &g_mapplanes[pidx];
if( PlaneEqual( p, normal, origin, dist ))
{
ThreadUnlock();
return p - g_mapplanes;
}
}
}
// allocate a new two opposite planes
int returnval = CreateNewFloatPlane( normal, origin );
ThreadUnlock();
return returnval;
}
int FindFloatPlane2( const vec3_t normal, const vec3_t origin )
{
int returnval;
plane_t *p, temp;
vec_t t;
returnval = 0;
find_plane:
for( ; returnval < g_nummapplanes; returnval++ )
{
// BUG: there might be some multithread issue --vluzacn
if( -DIR_EPSILON < ( t = normal[0] - g_mapplanes[returnval].normal[0] ) && t < DIR_EPSILON &&
-DIR_EPSILON < ( t = normal[1] - g_mapplanes[returnval].normal[1] ) && t < DIR_EPSILON &&
-DIR_EPSILON < ( t = normal[2] - g_mapplanes[returnval].normal[2] ) && t < DIR_EPSILON )
{
t = DotProduct (origin, g_mapplanes[returnval].normal) - g_mapplanes[returnval].dist;
if( -DIST_EPSILON < t && t < DIST_EPSILON )
return returnval;
}
}
ThreadLock();
if( returnval != g_nummapplanes ) // make sure we don't race
{
ThreadUnlock();
// check to see if other thread added plane we need
goto find_plane;
}
// create new planes - double check that we have room for 2 planes
if(( g_nummapplanes + 2 ) > MAX_INTERNAL_MAP_PLANES )
COM_FatalError( "MAX_INTERNAL_MAP_PLANES limit exceeded\n" );
p = &g_mapplanes[g_nummapplanes];
VectorCopy( origin, p->origin );
VectorCopy( normal, p->normal );
VectorNormalize( p->normal );
p->type = MapPlaneTypeForNormal( p->normal );
// snap normal to nearest axial if possible
if( p->type <= PLANE_LAST_AXIAL )
{
for( int i = 0; i < 3; i++ )
{
if( i == p->type )
p->normal[i] = p->normal[i] > 0 ? 1 : -1;
else p->normal[i] = 0;
}
}
p->dist = DotProduct( origin, p->normal );
VectorCopy( origin, (p+1)->origin );
VectorNegate( p->normal, (p+1)->normal );
(p+1)->type = p->type;
(p+1)->dist = -p->dist;
// always put axial planes facing positive first
if( normal[(p->type) % 3] < 0 )
{
temp = *p;
*p = *(p+1);
*(p+1) = temp;
returnval = g_nummapplanes + 1;
}
else
{
returnval = g_nummapplanes;
}
g_nummapplanes += 2;
ThreadUnlock();
return returnval;
}
/*
================
PlaneFromPoints
================
*/
int PlaneFromPoints( const vec_t *p0, const vec_t *p1, const vec_t *p2 )
{
vec3_t t1, t2, normal;
VectorSubtract( p0, p1, t1 );
VectorSubtract( p2, p1, t2 );
CrossProduct( t1, t2, normal );
if( !VectorNormalize( normal ))
return -1;
return FindFloatPlane( normal, p0 );
}
/*
=============================================================================
CONTENTS DETERMINING
=============================================================================
*/
/*
===========
BrushCheckSides
some compilers may use SKIP instead of NULL texture
replace it for consistency
===========
*/
void BrushCheckSides( mapent_t *mapent, brush_t *b )
{
int i, nodraw_faces = 0;
int skip_faces = 0;
int hint_faces = 0;
side_t *s;
// never apply this for a patches!
if( FBitSet( b->flags, FBRUSH_PATCH ))
return;
// explicit non-solid brushes also ignore it
if( FBitSet( b->flags, FBRUSH_NOCLIP ))
return;
for( i = 0; i < b->sides.Count(); i++ )
{
s = &b->sides[i];
if( FBitSet( s->flags, FSIDE_NODRAW ))
nodraw_faces++;
// count only non-shaders skip
if( FBitSet( s->flags, FSIDE_SKIP ) && FBitSet( s->shader->flags, FSHADER_DEFAULTED ))
skip_faces++;
if( FBitSet( s->flags, FSIDE_HINT|FSIDE_SOLIDHINT ))
hint_faces++;
}
// brush have "skip" faces and remaining faces not a hint
if( skip_faces && !hint_faces )
{
for( i = 0; i < b->sides.Count(); i++ )
{
s = &b->sides[i];
if( FBitSet( s->flags, FSIDE_SKIP ))
{
Q_strncpy( s->name, "NULL", sizeof( s->name ));
SetBits( s->flags, FSIDE_NODRAW );
ClearBits( s->flags, FSIDE_SKIP );
s->contents = CONTENTS_SOLID;
}
}
}
}
/*
===========
BrushContents
===========
*/
int BrushContents( mapent_t *mapent, brush_t *b )
{
const char *name = ValueForKey( (entity_t *)mapent, "classname" );
int i, best_i, contents;
bool assigned = false;
int nodraw_fases = 0;
int best_contents;
side_t *s;
// cycle though the sides of the brush and attempt to get our best side contents for
// determining overall brush contents
if( b->sides.Count() == 0 )
COM_FatalError( "Entity %i, Brush %i: Brush with no sides.\n", b->originalentitynum, b->originalbrushnum );
// for each face of each brush of this entity
if( BoolForKey( (entity_t *)mapent, "zhlt_precisionclip" ))
SetBits( b->flags, FBRUSH_PRECISIONCLIP );
// non-solid detail entity
if( !Q_stricmp( name, "func_detail_illusionary" ))
{
SetBits( b->flags, FBRUSH_NOCLIP );
b->detaillevel = 2;
return CONTENTS_EMPTY;
}
// solid detail entity
if( !Q_stricmp( name, "func_detail_fence" ) || !Q_stricmp( name, "func_detail_wall" ))
{
b->detaillevel = 1;
}
BrushCheckSides( mapent, b );
s = &b->sides[0];
// NULL can't be used to select contents
if( FBitSet( s->flags, FSIDE_NODRAW ))
best_contents = CONTENTS_NULL;
else best_contents = s->contents;
best_i = 0;
// SKIP doesn't split space in bsp process, ContentEmpty splits space normally.
if( FBitSet( s->flags, FSIDE_SKIP ))
assigned = true;
for( i = 1; i < b->sides.Count(); i++ )
{
s = &b->sides[i];
if( assigned ) break;
if( FBitSet( s->flags, FSIDE_SKIP ))
{
best_contents = s->contents;
assigned = true;
best_i = i;
}
// g-cont. NULL has lower priority than other contents
if( s->contents > best_contents && !FBitSet( s->flags, FSIDE_NODRAW ))
{
// if our current surface contents is better (larger)
// than our best, make it our best.
best_contents = s->contents;
best_i = i;
}
}
contents = best_contents;
for( i = 0; i < b->sides.Count(); i++ )
{
s = &b->sides[i];
if( FBitSet( s->flags, FSIDE_NODRAW ))
nodraw_fases++;
if( assigned && !FBitSet( s->flags, FSIDE_SKIP|FSIDE_HINT ) && s->contents != CONTENTS_ORIGIN )
continue; // overwrite content for this texture
// sky are not to cause mixed face contents
if( s->contents == CONTENTS_SKY || FBitSet( s->flags, FSIDE_NODRAW ))
continue;
if( s->contents != best_contents )
{
MsgDev( D_ERROR, "Entity %i, Brush %i: mixed face contents\n Texture %s and %s\n",
b->originalentitynum, b->originalbrushnum, b->sides[best_i].name, s->name );
break;
}
}
if( contents != CONTENTS_ORIGIN )
{
if( FBitSet( b->flags, FBRUSH_NOCLIP ))
contents = CONTENTS_EMPTY;
if( FBitSet( b->flags, FBRUSH_CLIPONLY ))
contents = CONTENTS_SOLID;
}
if( contents == CONTENTS_NULL )
contents = CONTENTS_SOLID;
// check to make sure we dont have an origin brush as part of worldspawn
if(( b->entitynum == 0 ) || ( !Q_strcmp( "func_group", name ) || !Q_strcmp( "func_landscape", name ) || !Q_strcmp( "func_detail", name )))
{
if( contents == CONTENTS_ORIGIN )
MsgDev( D_ERROR, "Entity %i, Brush %i: origin brush not allowed in world\n", b->originalentitynum, b->originalbrushnum );
}
else
{
// otherwise its not worldspawn, therefore its an entity. check to make sure this brush is allowed
// to be an entity.
switch( contents )
{
case CONTENTS_SOLID:
case CONTENTS_WATER:
case CONTENTS_SLIME:
case CONTENTS_LAVA:
case CONTENTS_ORIGIN:
case CONTENTS_EMPTY:
break;
default:
COM_FatalError( "Entity %i, Brush %i: %s brushes not allowed in entity",
b->originalentitynum, b->originalbrushnum, ContentsToString( contents ));
break;
}
}
// turn detail null-brushes into clipbrushes
if( nodraw_fases == b->sides.Count() && b->detaillevel )
SetBits( b->flags, FBRUSH_CLIPONLY );
// TyrTools style details: chop faces but ignore visportals
if( !Q_stricmp( "func_detail", name ) && ( contents == CONTENTS_SOLID || contents == CONTENTS_EMPTY ))
b->detaillevel++;
return contents;
}
/*
==============================================================================
BEVELED CLIPPING HULL GENERATION
This is done by brute force, and could easily get a lot faster if anyone cares.
==============================================================================
*/
#define MAX_HULL_POINTS 512
#define MAX_HULL_EDGES 1024
typedef struct expand_s
{
// input
brush_t *b;
int hullnum;
// output
vec3_t points[MAX_HULL_POINTS];
vec3_t corners[MAX_HULL_POINTS * 8];
int edges[MAX_HULL_EDGES][2];
int numpoints;
int numedges;
} expand_t;
/*
============
AddHullPlane
=============
*/
void AddHullPlane( expand_t *ex, const vec3_t normal, const vec3_t origin )
{
int planenum = FindFloatPlane( normal, origin );
brushhull_t *hull = &ex->b->hull[ex->hullnum];
for( bface_t *f = hull->faces; f != NULL; f = f->next )
{
if( f->planenum == planenum )
return; // don't add a plane twice
}
bface_t *face = AllocFace();
face->planenum = planenum;
face->plane = &g_mapplanes[face->planenum];
face->contents[0] = CONTENTS_EMPTY;
face->next = hull->faces;
hull->faces = face;
face->texinfo = -1;
}
/*
============
TestAddPlane
Adds the given plane to the brush description if all of the original brush
vertexes can be put on the front side
=============
*/
void TestAddPlane( expand_t *ex, const vec3_t normal, const vec3_t origin )
{
int planenum = FindFloatPlane( normal, origin );
brushhull_t *hull = &ex->b->hull[ex->hullnum];
int points_front, points_back;
vec_t *corner;
vec_t d;
// see if the plane has allready been added
for( bface_t *f = hull->faces; f != NULL; f = f->next )
{
if( f->planenum == planenum )
return; // don't add a plane twice
}
// check all the corner points
points_front = points_back = 0;
corner = ex->corners[0];
for( int i = 0; i < ex->numpoints * 8; i++, corner += 3 )
{
d = (corner[0] - origin[0]) * normal[0];
d += (corner[1] - origin[1]) * normal[1];
d += (corner[2] - origin[2]) * normal[2];
if( d < -ON_EPSILON )
{
if( points_front )
return;
points_back = 1;
}
else if( d > ON_EPSILON )
{
if( points_back )
return;
points_front = 1;
}
}
bface_t *face = AllocFace();
face->planenum = ( points_front ) ? planenum ^ 1 : planenum;
face->plane = &g_mapplanes[face->planenum];
face->contents[0] = CONTENTS_EMPTY;
face->next = hull->faces;
hull->faces = face;
face->texinfo = -1;
}
/*
============
AddHullPoint
Doesn't add if duplicated
=============
*/
int AddHullPoint( expand_t *ex, const vec3_t p )
{
vec_t *c;
int i;
for( i = 0; i < ex->numpoints; i++ )
{
if( VectorCompareEpsilon( p, ex->points[i], EQUAL_EPSILON ))
return i;
}
if( ex->numpoints == MAX_HULL_POINTS )
COM_FatalError( "MAX_HULL_POINTS limit exceeded\n" );
VectorCopy( p, ex->points[ex->numpoints] );
c = ex->corners[i*8];
ex->numpoints++;
// also add eight hull corners
for( int x = 0; x < 2; x++ )
{
for( int y = 0; y < 2; y++ )
{
for( int z = 0; z < 2; z++ )
{
c[0] = p[0] + g_hull_size[ex->hullnum][x][0];
c[1] = p[1] + g_hull_size[ex->hullnum][y][1];
c[2] = p[2] + g_hull_size[ex->hullnum][z][2];
c += 3;
}
}
}
return i;
}
/*
============
AddHullEdge
Creates all of the hull planes around the given edge, if not done already
=============
*/
void AddHullEdge( expand_t *ex, const vec3_t p1, const vec3_t p2 )
{
vec3_t edgevec, planevec;
vec3_t normal, origin;
int a, b, c, d, e;
int pt1, pt2;
vec_t length;
pt1 = AddHullPoint( ex, p1 );
pt2 = AddHullPoint( ex, p2 );
for( int i = 0; i < ex->numedges; i++ )
{
if(( ex->edges[i][0] == pt1 && ex->edges[i][1] == pt2 ) || ( ex->edges[i][0] == pt2 && ex->edges[i][1] == pt1 ))
return; // already added
}
if( ex->numedges == MAX_HULL_EDGES )
COM_FatalError( "MAX_HULL_EDGES limit exceeded\n" );
ex->edges[i][0] = pt1;
ex->edges[i][1] = pt2;
ex->numedges++;
VectorSubtract( p1, p2, edgevec );
VectorNormalize( edgevec );
for( a = 0; a < 3; a++ )
{
b = (a + 1) % 3;
c = (a + 2) % 3;
planevec[a] = 1.0;
planevec[b] = 0.0;
planevec[c] = 0.0;
CrossProduct( planevec, edgevec, normal );
length = VectorNormalize( normal );
// if this edge is almost parallel to the hull edge, skip it.
if( length < NORMAL_EPSILON ) continue;
for( d = 0; d <= 1; d++ )
{
for( e = 0; e <= 1; e++ )
{
VectorCopy( p1, origin );
origin[b] += g_hull_size[ex->hullnum][d][b];
origin[c] += g_hull_size[ex->hullnum][e][c];
TestAddPlane( ex, normal, origin );
}
}
}
}
/*
============
ExpandBrush
=============
*/
void ExpandBrush2( brush_t *b, int hullnum )
{
bface_t *brush_faces, *f;
vec3_t origin, normal;
bool axial = true;
int i, x, s;
vec_t corner;
brushhull_t *hull;
expand_t ex;
plane_t *p;
brush_faces = b->hull[0].faces;
hull = &b->hull[hullnum];
ex.hullnum = hullnum;
ex.numpoints = 0;
ex.numedges = 0;
ex.b = b;
// expand all of the planes
for( f = brush_faces; f != NULL; f = f->next )
{
p = f->plane;
if( p->type > PLANE_LAST_AXIAL )
axial = false; // not an xyz axial plane
VectorCopy( p->origin, origin );
VectorCopy( p->normal, normal );
for ( x = 0; x < 3; x++ )
{
if( p->normal[x] > 0.0 )
corner = g_hull_size[hullnum][1][x];
else if( p->normal[x] < 0.0 )
corner = -g_hull_size[hullnum][0][x];
else corner = 0.0;
origin[x] += p->normal[x] * corner;
}
bface_t *face = AllocFace();
face->planenum = FindFloatPlane( normal, origin );
face->plane = &g_mapplanes[face->planenum];
face->contents[0] = CONTENTS_EMPTY;
face->next = hull->faces;
hull->faces = face;
face->texinfo = -1;
}
// if this was an axial brush, we are done
if( axial ) return;
// add any axis planes not contained in the brush to bevel off corners
for( x = 0; x < 3; x++ )
{
for( s = -1; s <= 1; s += 2 )
{
// add the plane
VectorClear( normal );
normal[x] = s;
if( s == -1 ) VectorAdd( b->hull[0].mins, g_hull_size[hullnum][0], origin );
else VectorAdd( b->hull[0].maxs, g_hull_size[hullnum][1], origin );
AddHullPlane( &ex, normal, origin );
}
}
// create all the hull points
for( f = brush_faces; f != NULL; f = f->next )
{
for( i = 0; i < f->w->numpoints; i++ )
AddHullPoint( &ex, f->w->p[i] );
}
// add all of the edge bevels
for( f = brush_faces; f != NULL; f = f->next )
{
for( i = 0; i < f->w->numpoints; i++ )
AddHullEdge( &ex, f->w->p[i], f->w->p[(i + 1) % f->w->numpoints] );
}
}
/*
==============================================================================
BEVELED CLIPPING HULL GENERATION
This is done by brute force, and could easily get a lot faster if anyone cares.
==============================================================================
*/
// =====================================================================================
// AddHullPlane (subroutine for replacement of ExpandBrush, KGP)
// Called to add any and all clip hull planes by the new ExpandBrush.
// =====================================================================================
void AddHullPlane( brushhull_t *hull, const vec3_t normal, const vec3_t origin, bool check_planenum )
{
int planenum = FindFloatPlane( normal, origin );
// check to see if this plane is already in the brush (optional to speed
// up cases where we know the plane hasn't been added yet, like axial case)
if( check_planenum )
{
// we know axial planes are added in last step
if( g_mapplanes[planenum].type <= PLANE_LAST_AXIAL )
return;
for( bface_t *f = hull->faces; f != NULL; f = f->next )
{
if( f->planenum == planenum )
return; // don't add a plane twice
}
}
bface_t *face = AllocFace();
face->planenum = planenum;
face->plane = &g_mapplanes[face->planenum];
face->contents[0] = CONTENTS_EMPTY;
face->next = hull->faces;
hull->faces = face;
face->texinfo = -1;
}
/*
============
ExpandBrush
=============
*/
void ExpandBrush( brush_t *b, int hullnum )
{
vec3_t edge_start, edge_end;
plane_t *plane, *plane2;
vec3_t edge, bevel_edge;
bool start_found, end_found;
bool warned = false;
vec3_t origin, normal;
bface_t *f, *f2;
winding_t *w, *w2;
brushhull_t *hull;
hull = &b->hull[hullnum];
// step 1: for collision between player vertex and brush face. --vluzacn
for( f = b->hull[0].faces; f != NULL; f = f->next )
{
plane = f->plane;
// don't bother adding axial planes,
// they're defined by adding the bounding box anyway
if( plane->type <= PLANE_LAST_AXIAL )
continue;
// add the offset non-axial plane to the expanded hull
VectorCopy( plane->origin, origin );
VectorCopy( plane->normal, normal );
// old code multiplied offset by normal -- this led to post-csg "sticky" walls where a
// slope met an axial plane from the next brush since the offset from the slope would be less
// than the full offset for the axial plane -- the discontinuity also contributes to increased
// clipnodes. If the normal is zero along an axis, shifting the origin in that direction won't
// change the plane number, so I don't explicitly test that case. The old method is still used if
// preciseclip is turned off to allow backward compatability -- some of the improperly beveled edges
// grow using the new origins, and might cause additional problems.
origin[0] += g_hull_size[hullnum][(normal[0] > 0 ? 1 : 0)][0];
origin[1] += g_hull_size[hullnum][(normal[1] > 0 ? 1 : 0)][1];
origin[2] += g_hull_size[hullnum][(normal[2] > 0 ? 1 : 0)][2];
AddHullPlane( hull, normal, origin, false );
}
// step 2: for collision between player edge and brush edge. --vluzacn
// split bevel check into a second pass so we don't have to check for duplicate planes when adding offset planes
// in step above -- otherwise a bevel plane might duplicate an offset plane, causing problems later on.
for( f = b->hull[0].faces; f != NULL; f = f->next )
{
plane = f->plane;
// test to see if the plane is completely non-axial (if it is, need to add bevels to any
// existing "inflection edges" where there's a sign change with a neighboring plane's normal for
// a given axis)
// move along w and find plane on other side of each edge. If normals change sign,
// add a new plane by offsetting the points of the w to bevel the edge in that direction.
// It's possible to have inflection in multiple directions -- in this case, a new plane
// must be added for each sign change in the edge.
w = f->w;
// do it for each edge
for( int i = 0; i < w->numpoints; i++ )
{
VectorCopy( w->p[i], edge_start );
VectorCopy( w->p[(i + 1) % w->numpoints], edge_end );
// grab the edge (find relative length)
VectorSubtract( edge_end, edge_start, edge );
// brute force - need to check every other w for common points
// if the points match, the other face is the one we need to look at.
for( f2 = b->hull[0].faces; f2 != NULL; f2 = f2->next )
{
if( f == f2 ) continue;
start_found = false;
end_found = false;
w2 = f2->w;
for( int j = 0; j < w2->numpoints; j++ )
{
if( !start_found && VectorCompareEpsilon( w2->p[j], edge_start, EQUAL_EPSILON ))
start_found = true;
if( !end_found && VectorCompareEpsilon( w2->p[j], edge_end, EQUAL_EPSILON ))
end_found = true;
// we've found the face we want, move on to planar comparison
if( start_found && end_found )
break;
}
if( start_found && end_found )
break;
}
if( !f2 )
{
if( hullnum == 1 && !warned )
{
MsgDev( D_WARN, "Illegal Brush (edge without opposite face): Entity %i, Brush %i\n",
b->originalentitynum, b->originalbrushnum );
warned = true;
}
continue;
}
plane2 = f2->plane;
// check each direction for sign change in normal -- zero can be safely ignored
for( int dir = 0; dir < 3; dir++ )
{
// if sign changed, add bevel
if( plane->normal[dir] * plane2->normal[dir] < -NORMAL_EPSILON )
{
// pick direction of bevel edge by looking at normal of existing planes
VectorClear( bevel_edge );
bevel_edge[dir] = (plane->normal[dir] > 0) ? -1 : 1;
// find normal by taking normalized cross of the edge vector and the bevel edge
CrossProduct( edge, bevel_edge, normal );
VectorNormalize( normal );
if( fabs( normal[(dir+1)%3]) <= NORMAL_EPSILON || fabs( normal[(dir+2)%3] ) <= NORMAL_EPSILON )
{
// coincide with axial plane
continue;
}
// get the origin
VectorCopy( edge_start, origin );
// note: if normal == 0 in direction indicated, shifting origin doesn't change plane #
origin[0] += g_hull_size[hullnum][(normal[0] > 0 ? 1 : 0)][0];
origin[1] += g_hull_size[hullnum][(normal[1] > 0 ? 1 : 0)][1];
origin[2] += g_hull_size[hullnum][(normal[2] > 0 ? 1 : 0)][2];
// add the bevel plane to the expanded hull
AddHullPlane( hull, normal, origin, true );
}
}
}
}
// step 3: for collision between player face and brush vertex. --vluzacn
// add the bounding box to the expanded hull -- for a
// completely axial brush, this is the only necessary step
// add mins
VectorAdd( b->hull[0].mins, g_hull_size[hullnum][0], origin );
VectorSet( normal, -1.0, 0.0, 0.0 );
AddHullPlane( hull, normal, origin, false );
VectorSet( normal, 0.0, -1.0, 0.0 );
AddHullPlane( hull, normal, origin, false );
VectorSet( normal, 0.0, 0.0, -1.0 );
AddHullPlane( hull, normal, origin, false );
// add maxs
VectorAdd( b->hull[0].maxs, g_hull_size[hullnum][1], origin );
VectorSet( normal, 1.0, 0.0, 0.0 );
AddHullPlane( hull, normal, origin, false );
VectorSet( normal, 0.0, 1.0, 0.0 );
AddHullPlane( hull, normal, origin, false );
VectorSet( normal, 0.0, 0.0, 1.0 );
AddHullPlane( hull, normal, origin, false );
#if _DEBUG
// sanity check
for( f = hull->faces; f != NULL; f = f->next )
{
for( f2 = b->hull[0].faces; f2 != NULL; f2 = f2->next )
{
if( f2->w->numpoints < 3 )
continue;
for( int i = 0; i < f2->w->numpoints; i++ )
{
if( DotProduct( f->plane->normal, f->plane->origin ) < DotProduct( f->plane->normal, f2->w->p[i] ))
{
MsgDev( D_WARN, "Illegal Brush (clip hull [%i] has backward face): Entity %i, Brush %i\n",
hullnum, b->originalentitynum, b->originalbrushnum );
break;
}
}
}
}
#endif
}
//============================================================================
void FreeHullFaces( void )
{
for( int i = 0; i < g_nummapbrushes; i++ )
{
brush_t *b = &g_mapbrushes[i];
for( int j = 0; j < MAX_MAP_HULLS; j++ )
{
UnlinkFaces( &b->hull[j].faces );
}
// throw sides too
b->sides.Purge();
}
}
void SortHullFaces( brushhull_t *h )
{
int numfaces;
bface_t **faces;
vec3_t *normals;
bool *isused;
int i, j;
int *sorted;
bface_t *f;
for( numfaces = 0, f = h->faces; f != NULL; f = f->next )
numfaces++;
faces = (bface_t **)Mem_Alloc( numfaces * sizeof( bface_t* ), C_TEMPORARY );
normals = (vec3_t *)Mem_Alloc( numfaces * sizeof( vec3_t ), C_TEMPORARY );
isused = (bool *)Mem_Alloc( numfaces * sizeof( bool ), C_TEMPORARY );
sorted = (int *)Mem_Alloc( numfaces * sizeof( int ), C_TEMPORARY );
for( i = 0, f = h->faces; f != NULL; i++, f = f->next )
{
const plane_t *p = &g_mapplanes[f->planenum];
VectorCopy( p->normal, normals[i] );
faces[i] = f;
}
for( i = 0; i < numfaces; i++ )
{
int bestaxial = -1;
int bestside;
for( j = 0; j < numfaces; j++)
{
if( isused[j] ) continue;
int axial = AxisFromNormal( normals[j] );
if( axial > bestaxial )
{
bestaxial = axial;
bestside = j;
}
}
sorted[i] = bestside;
isused[bestside] = true;
}
for( i = -1; i < numfaces; i++ )
{
*(i >= 0 ? &faces[sorted[i]]->next: &h->faces) = (i + 1 < numfaces ? faces[sorted[i + 1]] : NULL);
}
Mem_Free( faces, C_TEMPORARY );
Mem_Free( normals, C_TEMPORARY );
Mem_Free( isused, C_TEMPORARY );
Mem_Free( sorted, C_TEMPORARY );
}
/*
===========
MakeHullFaces
===========
*/
void MakeHullFaces( brush_t *b, brushhull_t *h, int hullnum )
{
bface_t *f, *f2;
mapent_t *mapent;
bool warned = false;
vec_t v, area;
int i, j;
vec3_t point;
winding_t *w;
plane_t *p;
mapent = &g_mapentities[b->entitynum];
// sorted faces make BSP-splits is more axial than unsorted
SortHullFaces( h );
restart:
ClearBounds( h->mins, h->maxs );
for( f = h->faces; f != NULL; f = f->next )
{
w = BaseWindingForPlane( f->plane->normal, f->plane->dist );
ASSERT( w != NULL );
for( f2 = h->faces; f2 != NULL; f2 = f2->next )
{
if( f == f2 ) continue;
p = &g_mapplanes[f2->planenum ^ 1];
if( !ChopWindingInPlace( &w, p->normal, p->dist, NORMAL_EPSILON, false ))
break; // nothing to chop?
}
RemoveColinearPointsEpsilon( w, ON_EPSILON );
area = WindingArea( w );
if( area < MICROVOLUME )
{
if( w ) FreeWinding( w );
if( !w && !warned && hullnum == 0 )
{
MsgDev( D_WARN, "Illegal Brush (plane doesn't contribute to final shape): Entity %i, Brush %i\n",
b->originalentitynum, b->originalbrushnum );
warned = true;
}
// remove the face and regenerate the hull
UnlinkFaces( &h->faces, f );
goto restart;
}
f->contents[0] = CONTENTS_EMPTY;
f->w = w; // at this point winging is always valid
for( i = 0; i < w->numpoints; i++ )
{
for( j = 0; j < 3; j++ )
{
point[j] = w->p[i][j];
v = Q_rint( point[j] );
if( fabs( point[j] - v ) < DIR_EPSILON )
w->p[i][j] = v;
else w->p[i][j] = point[j];
// check for incomplete brushes
if( w->p[i][j] >= WORLD_MAXS || w->p[i][j] <= WORLD_MINS )
break;
}
// remove this brush
if( j < 3 )
{
UnlinkFaces( &h->faces );
MsgDev( D_REPORT, "Entity %i, Brush %i: degenerate brush was removed\n",
b->originalentitynum, b->originalbrushnum );
return;
}
if( !FBitSet( f->flags, FSIDE_SKIP ))
AddPointToBounds( w->p[i], h->mins, h->maxs );
}
// make sure what all faces has valid ws
CheckWindingEpsilon( w, ON_EPSILON, ( hullnum == 0 ));
}
for( i = 0; i < 3; i++ )
{
if( h->mins[i] < WORLD_MINS || h->maxs[i] > WORLD_MAXS )
{
MsgDev( D_ERROR, "Entity %i, Brush %i: outside world(+/-%d): (%.0f,%.0f,%.0f)-(%.0f,%.0f,%.0f)\n",
b->originalentitynum, b->originalbrushnum, BOGUS_RANGE / 2,
h->mins[0], h->mins[1], h->mins[2], h->maxs[0], h->maxs[1], h->maxs[2] );
break;
}
}
if( i == 3 && hullnum == 0 )
{
// compute total entity bounds
AddPointToBounds( h->mins, mapent->absmin, mapent->absmax );
AddPointToBounds( h->maxs, mapent->absmin, mapent->absmax );
}
}
/*
===========
MakeBrushPlanes
===========
*/
int MakeBrushPlanes( brush_t *b )
{
bool skipface;
int badsides = 0;
vec3_t planepts[3];
vec3_t origin;
bface_t *f;
side_t *s;
//
// if the origin key is set (by an origin brush), offset all of the values
//
GetVectorForKey( (entity_t *)&g_mapentities[b->entitynum], "origin", origin );
// convert to mapplanes
for( int i = 0; i < b->sides.Count(); i++ )
{
s = &b->sides[i];
skipface = false;
if( b->entitynum )
{
for( int k = 0; k < 3; k++ )
VectorSubtract( s->planepts[k], origin, planepts[k] );
s->planenum = PlaneFromPoints( planepts[0], planepts[1], planepts[2] );
}
else
{
// world doesn't required offset by origin
s->planenum = PlaneFromPoints( s->planepts[0], s->planepts[1], s->planepts[2] );
}
if( s->planenum == -1 )
{
MsgDev( D_REPORT, "Entity %i, Brush %i: plane with no normal\n", b->originalentitynum, b->originalbrushnum );
badsides++;
continue;
}
// see if the plane has been used already
for( f = b->hull[0].faces; f != NULL; f = f->next )
{
// g-cont. there is non fatal for us. just reject this face and trying again
if( f->planenum == s->planenum || f->planenum == ( s->planenum ^ 1 ))
{
MsgDev( D_REPORT, "Entity %i, Brush %i, Side %i: has a coplanar plane\n",
b->originalentitynum, b->originalbrushnum, i );
skipface = true;
badsides++;
break;
}
}
if( skipface ) continue;
f = AllocFace();
f->planenum = s->planenum;
f->plane = &g_mapplanes[s->planenum];
f->next = b->hull[0].faces;
b->hull[0].faces = f;
f->texinfo = g_onlyents ? -1 : TexinfoForSide( f->plane, s, origin );
f->flags = s->flags;
}
return badsides;
}
/*
===========
CreateBrushFaces
===========
*/
void CreateBrushFaces( brush_t *b )
{
// convert brush sides to planes
int badsides = MakeBrushPlanes( b );
if( badsides > 0 )
{
MsgDev( D_WARN, "Entity %i, Brush %i: has %d invalid sides (total %d)\n",
b->originalentitynum, b->originalbrushnum, badsides, b->sides.Count() );
}
MakeHullFaces( b, &b->hull[0], 0 );
if( b->contents == CONTENTS_EMPTY || b->contents == CONTENTS_ORIGIN )
return;
if( !g_noclip && ( FBitSet( b->flags, FBRUSH_CLIPONLY ) || !FBitSet( b->flags, FBRUSH_NOCLIP )))
{
for( int h = 1; h < MAX_MAP_HULLS; h++ )
{
if( VectorIsNull( g_hull_size[h][0] ) && VectorIsNull( g_hull_size[h][1] ))
continue;
if( FBitSet( b->flags, FBRUSH_PRECISIONCLIP ))
ExpandBrush2( b, h );
else ExpandBrush( b, h );
MakeHullFaces( b, &b->hull[h], h );
}
}
// invisible detail brush it's a clipbrush
if( FBitSet( b->flags, FBRUSH_CLIPONLY ))
{
UnlinkFaces( &b->hull[0].faces );
b->hull[0].faces = NULL;
}
}
/*
===========
CreateBrush
multi-thread version
===========
*/
void CreateBrush( int brushnum, int threadnum )
{
CreateBrushFaces( &g_mapbrushes[brushnum] );
}
/*
==================
DeleteBrushFaces
release specified face or purge all chain
==================
*/
void DeleteBrushFaces( brush_t *b )
{
if( !b ) return;
for( int i = 0; i < MAX_MAP_HULLS; i++ )
UnlinkFaces( &b->hull[i].faces );
}
/*
===========
DumpBrushPlanes
NOTE: never alloc planes during threads work because planes order will be
different for each compile and we gets various BSP'ing result every
time when we compile map again!
===========
*/
void DumpBrushPlanes( void )
{
#if 0
for( int i = 0; i < g_nummapbrushes; i++ )
{
brush_t *b = &g_mapbrushes[i];
if( b->contents == CONTENTS_ORIGIN )
continue;
Msg( "brush #%i\n", i );
// convert to mapplanes
for( int j = 0; j < b->sides.Count(); j++ )
{
side_t *s = &b->sides[j];
plane_t *mp = &g_mapplanes[s->planenum];
Msg( "#%i, (%g %g %g) - (%g), %d\n", j, mp->normal[0], mp->normal[1], mp->normal[2], mp->dist, mp->type );
}
}
#endif
}
void ProcessAutoOrigins( void )
{
char string[32];
const char *classname;
const char *pclassname;
const char *ptarget;
mapent_t *parent;
mapent_t *mapent;
vec3_t origin;
int c_origins_processed = 0;
bool origin_from_parent;
MsgDev( D_REPORT, "ProcessAutoOrigins:\n" );
// skip the world
for( int i = 1; i < g_mapentities.Count(); i++ )
{
origin_from_parent = false;
mapent = &g_mapentities[i];
parent = NULL;
if( !mapent->numbrushes )
continue;
// for some reasons entity doesn't have a valid size
if( BoundsIsCleared( mapent->absmin, mapent->absmax ))
continue;
GetVectorForKey( (entity_t *)mapent, "origin", origin );
// origin was set by level-designer
if( !VectorIsNull( origin ))
continue;
classname = ValueForKey( (entity_t *)mapent, "classname" );
// g-cont. old-good hack for my entity :-)
if( !Q_strcmp( classname, "func_traindoor" ))
{
if( CheckKey( (entity_t *)mapent, "train" ))
parent = FindTargetMapEntity( &g_mapentities, ValueForKey( (entity_t *)mapent, "train" ));
}
else if( !Q_strncmp( classname, "rotate_", 7 ))
{
// Quake1 rotational objects support
parent = FindTargetMapEntity( &g_mapentities, ValueForKey( (entity_t *)mapent, "target" ));
origin_from_parent = true;
}
else if( !Q_strncmp( classname, "func_portal", 11 ))
{
// portal always required origin-brush
parent = mapent;
}
if( !parent && CheckKey( (entity_t *)mapent, "parent" ))
parent = FindTargetMapEntity( &g_mapentities, ValueForKey( (entity_t *)mapent, "parent" ));
if( !parent ) continue;
pclassname = ValueForKey( (entity_t *)parent, "classname" );
ptarget = ValueForKey( (entity_t *)parent, "targetname" );
if( origin_from_parent || !Q_strcmp( pclassname, "func_door_rotating" ))
{
GetVectorForKey( (entity_t *)parent, "origin", origin );
Q_snprintf( string, sizeof( string ), "%.1f %.1f %.1f", origin[0], origin[1], origin[2] );
SetKeyValue( (entity_t *)mapent, "origin", string );
}
else
{
// now we have:
// 1. entity with valid absmin\absmax
// 2. it have valid parent
// 3. it doesn't have custom origin
VectorAverage( mapent->absmin, mapent->absmax, origin );
Q_snprintf( string, sizeof( string ), "%.1f %.1f %.1f", origin[0], origin[1], origin[2] );
SetKeyValue( (entity_t *)mapent, "origin", string );
}
MsgDev( D_REPORT, "%s, parent %s (%s) auto origin %g %g %g\n", classname, pclassname, ptarget, origin[0], origin[1], origin[2] );
for( int j = 0; j < mapent->numbrushes; j++ )
{
brush_t *b = &g_mapbrushes[mapent->firstbrush + j];
// remove old brush faces
DeleteBrushFaces( b );
// new brush with origin-adjusted sides
CreateBrushFaces( b );
}
c_origins_processed++;
}
if( c_origins_processed > 0 )
MsgDev( D_INFO, "total %i entities was adjusted with auto-origin\n", c_origins_processed );
}
void RestoreModelOrigins( void )
{
const char *pclassname;
const char *ptarget;
char string[32];
for( int i = 1; i < g_nummodels; i++ )
{
mapent_t *ent = MapEntityForModel( i );
dmodel_t *bm = &g_dmodels[i];
if( VectorIsNull( bm->origin ))
continue;
// for some reasons origin already set
if( CheckKey( (entity_t *)ent, "origin" ))
continue;
pclassname = ValueForKey( (entity_t *)ent, "classname" );
ptarget = ValueForKey( (entity_t *)ent, "targetname" );
MsgDev( D_REPORT, "%s, (%s) auto origin %g %g %g\n", pclassname, ptarget, bm->origin[0], bm->origin[1], bm->origin[2] );
Q_snprintf( string, sizeof( string ), "%.1f %.1f %.1f", bm->origin[0], bm->origin[1], bm->origin[2] );
SetKeyValue( (entity_t *)ent, "origin", string );
}
}