gtkradiant/tools/urt/tools/quake3/q3map2/brush.c

985 lines
19 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 BRUSH_C
/* dependencies */
#include "q3map2.h"
/* -------------------------------------------------------------------------------
functions
------------------------------------------------------------------------------- */
/*
AllocSideRef() - ydnar
allocates and assigns a brush side reference
*/
sideRef_t *AllocSideRef( side_t *side, sideRef_t *next ){
sideRef_t *sideRef;
/* dummy check */
if ( side == NULL ) {
return next;
}
/* allocate and return */
sideRef = safe_malloc( sizeof( *sideRef ) );
sideRef->side = side;
sideRef->next = next;
return sideRef;
}
/*
CountBrushList()
counts the number of brushes in a brush linked list
*/
int CountBrushList( brush_t *brushes ){
int c = 0;
/* count brushes */
for ( brushes; brushes != NULL; brushes = brushes->next )
c++;
return c;
}
/*
AllocBrush()
allocates a new brush
*/
brush_t *AllocBrush( int numSides ){
brush_t *bb;
int c;
/* allocate and clear */
if ( numSides <= 0 ) {
Error( "AllocBrush called with numsides = %d", numSides );
}
c = (int) &( ( (brush_t*) 0 )->sides[ numSides ] );
bb = safe_malloc( c );
memset( bb, 0, c );
if ( numthreads == 1 ) {
numActiveBrushes++;
}
/* return it */
return bb;
}
/*
FreeBrush()
frees a single brush and all sides/windings
*/
void FreeBrush( brush_t *b ){
int i;
/* error check */
if ( *( (unsigned int*) b ) == 0xFEFEFEFE ) {
Sys_FPrintf( SYS_VRB, "WARNING: Attempt to free an already freed brush!\n" );
return;
}
/* free brush sides */
for ( i = 0; i < b->numsides; i++ )
if ( b->sides[i].winding != NULL ) {
FreeWinding( b->sides[ i ].winding );
}
/* ydnar: overwrite it */
memset( b, 0xFE, (int) &( ( (brush_t*) 0 )->sides[ b->numsides ] ) );
*( (unsigned int*) b ) = 0xFEFEFEFE;
/* free it */
free( b );
if ( numthreads == 1 ) {
numActiveBrushes--;
}
}
/*
FreeBrushList()
frees a linked list of brushes
*/
void FreeBrushList( brush_t *brushes ){
brush_t *next;
/* walk brush list */
for ( brushes; brushes != NULL; brushes = next )
{
next = brushes->next;
FreeBrush( brushes );
}
}
/*
CopyBrush()
duplicates the brush, sides, and windings
*/
brush_t *CopyBrush( brush_t *brush ){
brush_t *newBrush;
int size;
int i;
/* copy brush */
size = (int) &( ( (brush_t*) 0 )->sides[ brush->numsides ] );
newBrush = AllocBrush( brush->numsides );
memcpy( newBrush, brush, size );
/* ydnar: nuke linked list */
newBrush->next = NULL;
/* copy sides */
for ( i = 0; i < brush->numsides; i++ )
{
if ( brush->sides[ i ].winding != NULL ) {
newBrush->sides[ i ].winding = CopyWinding( brush->sides[ i ].winding );
}
}
/* return it */
return newBrush;
}
/*
BoundBrush()
sets the mins/maxs based on the windings
returns false if the brush doesn't enclose a valid volume
*/
qboolean BoundBrush( brush_t *brush ){
int i, j;
winding_t *w;
ClearBounds( brush->mins, brush->maxs );
for ( i = 0; i < brush->numsides; i++ )
{
w = brush->sides[ i ].winding;
if ( w == NULL ) {
continue;
}
for ( j = 0; j < w->numpoints; j++ )
AddPointToBounds( w->p[ j ], brush->mins, brush->maxs );
}
for ( i = 0; i < 3; i++ )
{
if ( brush->mins[ i ] < MIN_WORLD_COORD || brush->maxs[ i ] > MAX_WORLD_COORD || brush->mins[i] >= brush->maxs[ i ] ) {
return qfalse;
}
}
return qtrue;
}
/*
SnapWeldVector() - ydnar
welds two vec3_t's into a third, taking into account nearest-to-integer
instead of averaging
*/
#define SNAP_EPSILON 0.01
void SnapWeldVector( vec3_t a, vec3_t b, vec3_t out ){
int i;
vec_t ai, bi, outi;
/* dummy check */
if ( a == NULL || b == NULL || out == NULL ) {
return;
}
/* do each element */
for ( i = 0; i < 3; i++ )
{
/* round to integer */
ai = Q_rint( a[ i ] );
bi = Q_rint( a[ i ] );
/* prefer exact integer */
if ( ai == a[ i ] ) {
out[ i ] = a[ i ];
}
else if ( bi == b[ i ] ) {
out[ i ] = b[ i ];
}
/* use nearest */
else if ( fabs( ai - a[ i ] ) < fabs( bi < b[ i ] ) ) {
out[ i ] = a[ i ];
}
else{
out[ i ] = b[ i ];
}
/* snap */
outi = Q_rint( out[ i ] );
if ( fabs( outi - out[ i ] ) <= SNAP_EPSILON ) {
out[ i ] = outi;
}
}
}
/*
FixWinding() - ydnar
removes degenerate edges from a winding
returns qtrue if the winding is valid
*/
#define DEGENERATE_EPSILON 0.1
qboolean FixWinding( winding_t *w ){
qboolean valid = qtrue;
int i, j, k;
vec3_t vec;
float dist;
/* dummy check */
if ( !w ) {
return qfalse;
}
/* check all verts */
for ( i = 0; i < w->numpoints; i++ )
{
/* don't remove points if winding is a triangle */
if ( w->numpoints == 3 ) {
return valid;
}
/* get second point index */
j = ( i + 1 ) % w->numpoints;
/* degenerate edge? */
VectorSubtract( w->p[ i ], w->p[ j ], vec );
dist = VectorLength( vec );
if ( dist < DEGENERATE_EPSILON ) {
valid = qfalse;
//Sys_FPrintf( SYS_VRB, "WARNING: Degenerate winding edge found, fixing...\n" );
/* create an average point (ydnar 2002-01-26: using nearest-integer weld preference) */
SnapWeldVector( w->p[ i ], w->p[ j ], vec );
VectorCopy( vec, w->p[ i ] );
//VectorAdd( w->p[ i ], w->p[ j ], vec );
//VectorScale( vec, 0.5, w->p[ i ] );
/* move the remaining verts */
for ( k = i + 2; k < w->numpoints; k++ )
{
VectorCopy( w->p[ k ], w->p[ k - 1 ] );
}
w->numpoints--;
}
}
/* one last check and return */
if ( w->numpoints < 3 ) {
valid = qfalse;
}
return valid;
}
/*
CreateBrushWindings()
makes basewindigs for sides and mins/maxs for the brush
returns false if the brush doesn't enclose a valid volume
*/
qboolean CreateBrushWindings( brush_t *brush ){
int i, j;
winding_t *w;
side_t *side;
plane_t *plane;
/* walk the list of brush sides */
for ( i = 0; i < brush->numsides; i++ )
{
/* get side and plane */
side = &brush->sides[ i ];
plane = &mapplanes[ side->planenum ];
/* make huge winding */
w = BaseWindingForPlane( plane->normal, plane->dist );
/* walk the list of brush sides */
for ( j = 0; j < brush->numsides && w != NULL; j++ )
{
if ( i == j ) {
continue;
}
if ( brush->sides[ j ].planenum == ( brush->sides[ i ].planenum ^ 1 ) ) {
continue; /* back side clipaway */
}
if ( brush->sides[ j ].bevel ) {
continue;
}
plane = &mapplanes[ brush->sides[ j ].planenum ^ 1 ];
ChopWindingInPlace( &w, plane->normal, plane->dist, 0 ); // CLIP_EPSILON );
/* ydnar: fix broken windings that would generate trifans */
FixWinding( w );
}
/* set side winding */
side->winding = w;
}
/* find brush bounds */
return BoundBrush( brush );
}
/*
==================
BrushFromBounds
Creates a new axial brush
==================
*/
brush_t *BrushFromBounds( vec3_t mins, vec3_t maxs ){
brush_t *b;
int i;
vec3_t normal;
vec_t dist;
b = AllocBrush( 6 );
b->numsides = 6;
for ( i = 0 ; i < 3 ; i++ )
{
VectorClear( normal );
normal[i] = 1;
dist = maxs[i];
b->sides[i].planenum = FindFloatPlane( normal, dist, 1, (vec3_t*) &maxs );
normal[i] = -1;
dist = -mins[i];
b->sides[3 + i].planenum = FindFloatPlane( normal, dist, 1, (vec3_t*) &mins );
}
CreateBrushWindings( b );
return b;
}
/*
==================
BrushVolume
==================
*/
vec_t BrushVolume( brush_t *brush ){
int i;
winding_t *w;
vec3_t corner;
vec_t d, area, volume;
plane_t *plane;
if ( !brush ) {
return 0;
}
// grab the first valid point as the corner
w = NULL;
for ( i = 0 ; i < brush->numsides ; i++ )
{
w = brush->sides[i].winding;
if ( w ) {
break;
}
}
if ( !w ) {
return 0;
}
VectorCopy( w->p[0], corner );
// make tetrahedrons to all other faces
volume = 0;
for ( ; i < brush->numsides ; i++ )
{
w = brush->sides[i].winding;
if ( !w ) {
continue;
}
plane = &mapplanes[brush->sides[i].planenum];
d = -( DotProduct( corner, plane->normal ) - plane->dist );
area = WindingArea( w );
volume += d * area;
}
volume /= 3;
return volume;
}
/*
WriteBSPBrushMap()
writes a map with the split bsp brushes
*/
void WriteBSPBrushMap( char *name, brush_t *list ){
FILE *f;
side_t *s;
int i;
winding_t *w;
/* note it */
Sys_Printf( "Writing %s\n", name );
/* open the map file */
f = fopen( name, "wb" );
if ( f == NULL ) {
Error( "Can't write %s\b", name );
}
fprintf( f, "{\n\"classname\" \"worldspawn\"\n" );
for ( ; list ; list = list->next )
{
fprintf( f, "{\n" );
for ( i = 0,s = list->sides ; i < list->numsides ; i++,s++ )
{
w = BaseWindingForPlane( mapplanes[s->planenum].normal, mapplanes[s->planenum].dist );
fprintf( f,"( %i %i %i ) ", (int)w->p[0][0], (int)w->p[0][1], (int)w->p[0][2] );
fprintf( f,"( %i %i %i ) ", (int)w->p[1][0], (int)w->p[1][1], (int)w->p[1][2] );
fprintf( f,"( %i %i %i ) ", (int)w->p[2][0], (int)w->p[2][1], (int)w->p[2][2] );
fprintf( f, "notexture 0 0 0 1 1\n" );
FreeWinding( w );
}
fprintf( f, "}\n" );
}
fprintf( f, "}\n" );
fclose( f );
}
/*
FilterBrushIntoTree_r()
adds brush reference to any intersecting bsp leafnode
*/
int FilterBrushIntoTree_r( brush_t *b, node_t *node ){
brush_t *front, *back;
int c;
/* dummy check */
if ( b == NULL ) {
return 0;
}
/* add it to the leaf list */
if ( node->planenum == PLANENUM_LEAF ) {
/* something somewhere is hammering brushlist */
b->next = node->brushlist;
node->brushlist = b;
/* classify the leaf by the structural brush */
if ( !b->detail ) {
if ( b->opaque ) {
node->opaque = qtrue;
node->areaportal = qfalse;
}
else if ( b->compileFlags & C_AREAPORTAL ) {
if ( !node->opaque ) {
node->areaportal = qtrue;
}
}
}
return 1;
}
/* split it by the node plane */
c = b->numsides;
SplitBrush( b, node->planenum, &front, &back );
FreeBrush( b );
c = 0;
c += FilterBrushIntoTree_r( front, node->children[ 0 ] );
c += FilterBrushIntoTree_r( back, node->children[ 1 ] );
return c;
}
/*
FilterDetailBrushesIntoTree
fragment all the detail brushes into the structural leafs
*/
void FilterDetailBrushesIntoTree( entity_t *e, tree_t *tree ){
brush_t *b, *newb;
int r;
int c_unique, c_clusters;
int i;
/* note it */
Sys_FPrintf( SYS_VRB, "--- FilterDetailBrushesIntoTree ---\n" );
/* walk the list of brushes */
c_unique = 0;
c_clusters = 0;
for ( b = e->brushes; b; b = b->next )
{
if ( !b->detail ) {
continue;
}
c_unique++;
newb = CopyBrush( b );
r = FilterBrushIntoTree_r( newb, tree->headnode );
c_clusters += r;
/* mark all sides as visible so drawsurfs are created */
if ( r ) {
for ( i = 0; i < b->numsides; i++ )
{
if ( b->sides[ i ].winding ) {
b->sides[ i ].visible = qtrue;
}
}
}
}
/* emit some statistics */
Sys_FPrintf( SYS_VRB, "%9d detail brushes\n", c_unique );
Sys_FPrintf( SYS_VRB, "%9d cluster references\n", c_clusters );
}
/*
=====================
FilterStructuralBrushesIntoTree
Mark the leafs as opaque and areaportals
=====================
*/
void FilterStructuralBrushesIntoTree( entity_t * e, tree_t * tree ){
brush_t *b, *newb;
int r;
int c_unique, c_clusters;
int i;
Sys_FPrintf( SYS_VRB, "--- FilterStructuralBrushesIntoTree ---\n" );
c_unique = 0;
c_clusters = 0;
for ( b = e->brushes; b; b = b->next )
{
if ( b->detail ) {
continue;
}
c_unique++;
newb = CopyBrush( b );
r = FilterBrushIntoTree_r( newb, tree->headnode );
c_clusters += r;
// mark all sides as visible so drawsurfs are created
if ( r ) {
for ( i = 0; i < b->numsides; i++ )
{
if ( b->sides[i].winding ) {
b->sides[i].visible = qtrue;
}
}
}
}
/* emit some statistics */
Sys_FPrintf( SYS_VRB, "%9d structural brushes\n", c_unique );
Sys_FPrintf( SYS_VRB, "%9d cluster references\n", c_clusters );
}
/*
================
AllocTree
================
*/
tree_t *AllocTree( void ){
tree_t *tree;
tree = safe_malloc( sizeof( *tree ) );
memset( tree, 0, sizeof( *tree ) );
ClearBounds( tree->mins, tree->maxs );
return tree;
}
/*
================
AllocNode
================
*/
node_t *AllocNode( void ){
node_t *node;
node = safe_malloc( sizeof( *node ) );
memset( node, 0, sizeof( *node ) );
return node;
}
/*
================
WindingIsTiny
Returns true if the winding would be crunched out of
existance by the vertex snapping.
================
*/
#define EDGE_LENGTH 0.2
qboolean WindingIsTiny( winding_t *w ){
/*
if (WindingArea (w) < 1)
return qtrue;
return qfalse;
*/
int i, j;
vec_t len;
vec3_t delta;
int edges;
edges = 0;
for ( i = 0 ; i < w->numpoints ; i++ )
{
j = i == w->numpoints - 1 ? 0 : i + 1;
VectorSubtract( w->p[j], w->p[i], delta );
len = VectorLength( delta );
if ( len > EDGE_LENGTH ) {
if ( ++edges == 3 ) {
return qfalse;
}
}
}
return qtrue;
}
/*
================
WindingIsHuge
Returns true if the winding still has one of the points
from basewinding for plane
================
*/
qboolean WindingIsHuge( winding_t *w ){
int i, j;
for ( i = 0 ; i < w->numpoints ; i++ )
{
for ( j = 0 ; j < 3 ; j++ )
if ( w->p[i][j] <= MIN_WORLD_COORD || w->p[i][j] >= MAX_WORLD_COORD ) {
return qtrue;
}
}
return qfalse;
}
//============================================================
/*
==================
BrushMostlyOnSide
==================
*/
int BrushMostlyOnSide( brush_t *brush, plane_t *plane ){
int i, j;
winding_t *w;
vec_t d, max;
int side;
max = 0;
side = PSIDE_FRONT;
for ( i = 0 ; i < brush->numsides ; i++ )
{
w = brush->sides[i].winding;
if ( !w ) {
continue;
}
for ( j = 0 ; j < w->numpoints ; j++ )
{
d = DotProduct( w->p[j], plane->normal ) - plane->dist;
if ( d > max ) {
max = d;
side = PSIDE_FRONT;
}
if ( -d > max ) {
max = -d;
side = PSIDE_BACK;
}
}
}
return side;
}
/*
SplitBrush()
generates two new brushes, leaving the original unchanged
*/
void SplitBrush( brush_t *brush, int planenum, brush_t **front, brush_t **back ){
brush_t *b[2];
int i, j;
winding_t *w, *cw[2], *midwinding;
plane_t *plane, *plane2;
side_t *s, *cs;
float d, d_front, d_back;
*front = NULL;
*back = NULL;
plane = &mapplanes[planenum];
// check all points
d_front = d_back = 0;
for ( i = 0 ; i < brush->numsides ; i++ )
{
w = brush->sides[i].winding;
if ( !w ) {
continue;
}
for ( j = 0 ; j < w->numpoints ; j++ )
{
d = DotProduct( w->p[j], plane->normal ) - plane->dist;
if ( d > 0 && d > d_front ) {
d_front = d;
}
if ( d < 0 && d < d_back ) {
d_back = d;
}
}
}
if ( d_front < 0.1 ) { // PLANESIDE_EPSILON)
// only on back
*back = CopyBrush( brush );
return;
}
if ( d_back > -0.1 ) { // PLANESIDE_EPSILON)
// only on front
*front = CopyBrush( brush );
return;
}
// create a new winding from the split plane
w = BaseWindingForPlane( plane->normal, plane->dist );
for ( i = 0 ; i < brush->numsides && w ; i++ )
{
plane2 = &mapplanes[brush->sides[i].planenum ^ 1];
ChopWindingInPlace( &w, plane2->normal, plane2->dist, 0 ); // PLANESIDE_EPSILON);
}
if ( !w || WindingIsTiny( w ) ) { // the brush isn't really split
int side;
side = BrushMostlyOnSide( brush, plane );
if ( side == PSIDE_FRONT ) {
*front = CopyBrush( brush );
}
if ( side == PSIDE_BACK ) {
*back = CopyBrush( brush );
}
return;
}
if ( WindingIsHuge( w ) ) {
Sys_FPrintf( SYS_VRB,"WARNING: huge winding\n" );
}
midwinding = w;
// split it for real
for ( i = 0 ; i < 2 ; i++ )
{
b[i] = AllocBrush( brush->numsides + 1 );
memcpy( b[i], brush, sizeof( brush_t ) - sizeof( brush->sides ) );
b[i]->numsides = 0;
b[i]->next = NULL;
b[i]->original = brush->original;
}
// split all the current windings
for ( i = 0 ; i < brush->numsides ; i++ )
{
s = &brush->sides[i];
w = s->winding;
if ( !w ) {
continue;
}
ClipWindingEpsilon( w, plane->normal, plane->dist, 0 /*PLANESIDE_EPSILON */, &cw[0], &cw[1] );
for ( j = 0 ; j < 2 ; j++ )
{
if ( !cw[j] ) {
continue;
}
cs = &b[j]->sides[b[j]->numsides];
b[j]->numsides++;
*cs = *s;
cs->winding = cw[j];
}
}
// see if we have valid polygons on both sides
for ( i = 0 ; i < 2 ; i++ )
{
BoundBrush( b[i] );
for ( j = 0 ; j < 3 ; j++ )
{
if ( b[i]->mins[j] < MIN_WORLD_COORD || b[i]->maxs[j] > MAX_WORLD_COORD ) {
Sys_FPrintf( SYS_VRB,"bogus brush after clip\n" );
break;
}
}
if ( b[i]->numsides < 3 || j < 3 ) {
FreeBrush( b[i] );
b[i] = NULL;
}
}
if ( !( b[0] && b[1] ) ) {
if ( !b[0] && !b[1] ) {
Sys_FPrintf( SYS_VRB,"split removed brush\n" );
}
else{
Sys_FPrintf( SYS_VRB,"split not on both sides\n" );
}
if ( b[0] ) {
FreeBrush( b[0] );
*front = CopyBrush( brush );
}
if ( b[1] ) {
FreeBrush( b[1] );
*back = CopyBrush( brush );
}
return;
}
// add the midwinding to both sides
for ( i = 0 ; i < 2 ; i++ )
{
cs = &b[i]->sides[b[i]->numsides];
b[i]->numsides++;
cs->planenum = planenum ^ i ^ 1;
cs->shaderInfo = NULL;
if ( i == 0 ) {
cs->winding = CopyWinding( midwinding );
}
else{
cs->winding = midwinding;
}
}
{
vec_t v1;
int i;
for ( i = 0 ; i < 2 ; i++ )
{
v1 = BrushVolume( b[i] );
if ( v1 < 1.0 ) {
FreeBrush( b[i] );
b[i] = NULL;
// Sys_FPrintf (SYS_VRB,"tiny volume after clip\n");
}
}
}
*front = b[0];
*back = b[1];
}