forked from FWGS/Paranoia2
586 lines
12 KiB
C++
586 lines
12 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.
|
|
*
|
|
****/
|
|
|
|
#include "csg.h"
|
|
|
|
int c_outfaces;
|
|
int g_firstbrush;
|
|
|
|
/*
|
|
===========
|
|
AllocFace
|
|
===========
|
|
*/
|
|
bface_t *AllocFace( void )
|
|
{
|
|
bface_t *f;
|
|
|
|
f = (bface_t *)Mem_Alloc( sizeof( bface_t ), C_SURFACE );
|
|
// ClearBounds( f->mins, f->maxs );
|
|
f->planenum = -1;
|
|
// f->texinfo = -1;
|
|
|
|
return f;
|
|
}
|
|
|
|
/*
|
|
==================
|
|
FreeFace
|
|
==================
|
|
*/
|
|
void FreeFace( bface_t *f )
|
|
{
|
|
if( !f ) return;
|
|
|
|
if( f->w ) FreeWinding( f->w );
|
|
Mem_Free( f, C_SURFACE );
|
|
}
|
|
|
|
/*
|
|
==================
|
|
NewFaceFromFace
|
|
|
|
Duplicates the non point information of a face, used by SplitFace
|
|
==================
|
|
*/
|
|
bface_t *NewFaceFromFace( const bface_t *in )
|
|
{
|
|
bface_t *newf;
|
|
|
|
newf = AllocFace();
|
|
newf->contents[0] = in->contents[0];
|
|
newf->contents[1] = in->contents[1];
|
|
newf->planenum = in->planenum;
|
|
newf->texinfo = in->texinfo;
|
|
newf->plane = in->plane;
|
|
newf->flags = in->flags;
|
|
|
|
return newf;
|
|
}
|
|
|
|
/*
|
|
==================
|
|
CopyFace
|
|
==================
|
|
*/
|
|
bface_t *CopyFace( const bface_t *f )
|
|
{
|
|
bface_t *n;
|
|
|
|
n = NewFaceFromFace( f );
|
|
n->w = CopyWinding( f->w );
|
|
VectorCopy( f->mins, n->mins );
|
|
VectorCopy( f->maxs, n->maxs );
|
|
|
|
return n;
|
|
}
|
|
|
|
/*
|
|
==================
|
|
CopyFacesToOutside
|
|
|
|
Make a copy of all the faces of the brush, so they
|
|
can be chewed up by other brushes.
|
|
|
|
All of the faces start on the outside list.
|
|
As other brushes take bites out of the faces, the
|
|
fragments are moved to the inside list, so they
|
|
can be freed when they are determined to be
|
|
completely enclosed in solid.
|
|
==================
|
|
*/
|
|
bface_t *CopyFacesToOutside( brush_t *b, int num )
|
|
{
|
|
bface_t *f, *newf;
|
|
bface_t *outside;
|
|
|
|
outside = NULL;
|
|
|
|
for( f = b->hull[num].faces; f != NULL; f = f->next )
|
|
{
|
|
newf = CopyFace( f );
|
|
WindingBounds( newf->w, newf->mins, newf->maxs );
|
|
newf->contents[1] = b->contents;
|
|
newf->next = outside;
|
|
outside = newf;
|
|
}
|
|
|
|
return outside;
|
|
}
|
|
|
|
/*
|
|
==================
|
|
UnlinkFaces
|
|
|
|
release specified face or purge all chain
|
|
==================
|
|
*/
|
|
void UnlinkFaces( bface_t **head, bface_t *face )
|
|
{
|
|
bface_t **prev = head;
|
|
bface_t *cur;
|
|
|
|
while( 1 )
|
|
{
|
|
cur = *prev;
|
|
if( !cur ) break;
|
|
|
|
if( face != NULL && face != cur )
|
|
{
|
|
prev = &cur->next;
|
|
continue;
|
|
}
|
|
|
|
// unlink face from list
|
|
*prev = cur->next;
|
|
FreeFace( cur );
|
|
}
|
|
}
|
|
|
|
//============================================================
|
|
/*
|
|
==================
|
|
SaveOutside
|
|
|
|
The faces remaining on the outside list are final
|
|
polygons. Write them to the output file.
|
|
|
|
Passable contents (water, lava, etc) will generate
|
|
a mirrored copy of the face to be seen from the inside.
|
|
==================
|
|
*/
|
|
static void SaveOutside( brush_t *b, int hull, bface_t *outside )
|
|
{
|
|
bface_t *f, *f2, *next;
|
|
|
|
for( f = outside; f != NULL; f = next )
|
|
{
|
|
int frontcontents = f->contents[0];
|
|
int texinfo = f->texinfo;
|
|
bool frontnull = false;
|
|
bool backnull = false;
|
|
int backcontents;
|
|
|
|
next = f->next;
|
|
|
|
if( b->contents == CONTENTS_EMPTY )
|
|
backcontents = f->contents[1];
|
|
else backcontents = b->contents;
|
|
|
|
if( b->contents == CONTENTS_EMPTY )
|
|
{
|
|
// SKIP and HINT are special textures for hlbsp
|
|
if( !FBitSet( f->flags, FSIDE_HINT|FSIDE_SKIP|FSIDE_SOLIDHINT ))
|
|
backnull = true;
|
|
}
|
|
|
|
if( FBitSet( f->flags, FSIDE_SOLIDHINT ))
|
|
{
|
|
if( frontcontents != backcontents )
|
|
frontnull = backnull = true; // not discardable, so remove "SOLIDHINT" texture name and behave like NULL
|
|
}
|
|
|
|
if( b->entitynum != 0 && IsLiquidContents( f->contents[0] ))
|
|
backnull = true; // strip water face on one side
|
|
|
|
if( WindingArea( f->w ) <= 0.0 )
|
|
{
|
|
MsgDev( D_WARN, "Entity %i, Brush %i: tiny fragment\n", b->originalentitynum, b->originalbrushnum );
|
|
FreeFace( f );
|
|
continue;
|
|
}
|
|
|
|
if( hull == HULL_VISIBLE )
|
|
{
|
|
// count unique faces for visible hull
|
|
for( f2 = b->hull[hull].faces; f2 != NULL; f2 = f2->next )
|
|
{
|
|
if( f2->planenum == f->planenum )
|
|
{
|
|
if( !FBitSet( f2->flags, FSIDE_USED ))
|
|
{
|
|
SetBits( f2->flags, FSIDE_USED );
|
|
c_outfaces++;
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
f->contents[0] = frontcontents;
|
|
f->texinfo = frontnull ? -1 : texinfo;
|
|
|
|
// save front face
|
|
if( hull == HULL_VISIBLE )
|
|
EmitFace( hull, f, b->detaillevel );
|
|
else EmitFace( hull, f, 0 );
|
|
|
|
// filp face
|
|
f->planenum ^= 1; // reverse side
|
|
f->plane = &g_mapplanes[f->planenum];
|
|
f->contents[0] = backcontents;
|
|
f->texinfo = backnull ? -1 : texinfo;
|
|
ReverseWinding( &f->w );
|
|
|
|
// save back face
|
|
if( hull == HULL_VISIBLE )
|
|
EmitFace( hull, f, b->detaillevel );
|
|
else EmitFace( hull, f, 0 );
|
|
|
|
FreeFace( f );
|
|
}
|
|
}
|
|
|
|
//==========================================================================
|
|
/*
|
|
===========
|
|
CSGBrush
|
|
===========
|
|
*/
|
|
static void CSGBrush( int brushnum, int threadnum = -1 )
|
|
{
|
|
bface_t *f, *f2, *next;
|
|
brushhull_t *bh1, *bh2;
|
|
bool overwrite;
|
|
brush_t *b1, *b2;
|
|
bface_t *outside;
|
|
vec_t area;
|
|
mapent_t *e;
|
|
|
|
brushnum = g_firstbrush + brushnum;
|
|
b1 = &g_mapbrushes[brushnum];
|
|
e = &g_mapentities[b1->entitynum];
|
|
|
|
// for each of the hulls
|
|
for( int hull = 0; hull < MAX_MAP_HULLS; hull++ )
|
|
{
|
|
bh1 = &b1->hull[hull];
|
|
|
|
// g-cont. if brush detaillevel is 0 it's not a detailbrush!
|
|
if( bh1->faces && b1->csg_detaillevel( hull ) > 0 )
|
|
{
|
|
switch( b1->contents )
|
|
{
|
|
case CONTENTS_EMPTY:
|
|
break;
|
|
case CONTENTS_SOLID:
|
|
EmitDetailBrush( hull, bh1->faces );
|
|
break;
|
|
default: // g-cont. detail brushes can't be a liquid
|
|
COM_FatalError( "Entity %i, Brush %i: not allowed in detail\n", b1->originalentitynum, b1->originalbrushnum );
|
|
break;
|
|
}
|
|
}
|
|
|
|
// set outside to a copy of the brush's faces
|
|
outside = CopyFacesToOutside( b1, hull );
|
|
overwrite = false;
|
|
|
|
if( b1->contents == CONTENTS_EMPTY )
|
|
{
|
|
for( f = outside; f != NULL; f = f->next )
|
|
{
|
|
f->contents[0] = CONTENTS_EMPTY;
|
|
f->contents[1] = CONTENTS_EMPTY;
|
|
}
|
|
}
|
|
|
|
// for each brush in entity e
|
|
for( int bn = 0; bn < e->numbrushes; bn++ )
|
|
{
|
|
// see if b2 needs to clip a chunk out of b1
|
|
if( e->firstbrush + bn == brushnum )
|
|
continue;
|
|
|
|
overwrite = (e->firstbrush + bn > brushnum) ? true : false;
|
|
b2 = &g_mapbrushes[e->firstbrush + bn];
|
|
bh2 = &b2->hull[hull];
|
|
|
|
if( b2->contents == CONTENTS_EMPTY )
|
|
continue;
|
|
|
|
if( b2->csg_detaillevel( hull ) > b1->csg_detaillevel( hull ))
|
|
continue; // you can't chop
|
|
|
|
// check if brush1 can overwrite brush2
|
|
if( b2->contents == b1->contents )
|
|
{
|
|
if( b1->csg_detaillevel( hull ) != b2->csg_detaillevel( hull ))
|
|
{
|
|
overwrite = (b2->csg_detaillevel( hull ) < b1->csg_detaillevel( hull )) ? true : false;
|
|
}
|
|
}
|
|
|
|
if(( !bh2->faces ) || ( hull == 0 && FBitSet( b2->flags, FBRUSH_NOCSG )))
|
|
continue; // brush isn't in this hull
|
|
|
|
if( !BoundsIntersect( bh1->mins, bh1->maxs, bh2->mins, bh2->maxs ))
|
|
continue;
|
|
|
|
// divide faces by the planes of the b2 to find which
|
|
// fragments are inside
|
|
for( f = outside, outside = NULL; f != NULL; f = next )
|
|
{
|
|
next = f->next;
|
|
|
|
if( !BoundsIntersect( bh2->mins, bh2->maxs, f->mins, f->maxs ))
|
|
{
|
|
// this face doesn't intersect brush2's bbox
|
|
f->next = outside;
|
|
outside = f;
|
|
continue;
|
|
}
|
|
|
|
if( b2->csg_detaillevel( hull ) > b1->csg_detaillevel( hull ))
|
|
{
|
|
if( FBitSet( f->flags, FSIDE_NODRAW|FSIDE_SKIP|FSIDE_HINT|FSIDE_SOLIDHINT ))
|
|
{
|
|
// should not nullify the fragment inside detail brush
|
|
f->next = outside;
|
|
outside = f;
|
|
continue;
|
|
}
|
|
}
|
|
|
|
// throw pieces on the front sides of the planes
|
|
// into the outside list, return the remains on the inside
|
|
|
|
// find the fragment inside brush2
|
|
winding_t *w = CopyWinding( f->w );
|
|
|
|
for( f2 = bh2->faces; f2 != NULL; f2 = f2->next )
|
|
{
|
|
if( f->planenum == f2->planenum )
|
|
{
|
|
if( !overwrite || FBitSet( f2->flags, FSIDE_NODRAW ))
|
|
{
|
|
// face plane is outside brush2
|
|
FreeWinding( w );
|
|
w = NULL;
|
|
break;
|
|
}
|
|
else
|
|
{
|
|
continue;
|
|
}
|
|
}
|
|
|
|
if( f->planenum == ( f2->planenum ^ 1 ))
|
|
{
|
|
#if 0
|
|
if( FBitSet( f2->flags, FSIDE_NODRAW ))
|
|
{
|
|
// face plane is outside brush2
|
|
FreeWinding( w );
|
|
w = NULL;
|
|
break;
|
|
}
|
|
else
|
|
#endif
|
|
{
|
|
continue;
|
|
}
|
|
}
|
|
|
|
winding_t *fw, *bw;
|
|
|
|
ClipWindingEpsilon( w, f2->plane->normal, f2->plane->dist, g_csgepsilon, &fw, &bw );
|
|
|
|
if( fw )
|
|
{
|
|
FreeWinding( fw );
|
|
}
|
|
|
|
if( bw )
|
|
{
|
|
FreeWinding( w );
|
|
w = bw;
|
|
}
|
|
else
|
|
{
|
|
FreeWinding( w );
|
|
w = NULL;
|
|
break;
|
|
}
|
|
}
|
|
|
|
// do real split
|
|
if( w != NULL )
|
|
{
|
|
for( f2 = bh2->faces; f2 != NULL; f2 = f2->next )
|
|
{
|
|
if( f->planenum == f2->planenum || f->planenum == ( f2->planenum ^ 1 ))
|
|
{
|
|
continue;
|
|
}
|
|
|
|
int valid = 0;
|
|
|
|
for( int x = 0; x < w->numpoints; x++ )
|
|
{
|
|
vec_t dist = DotProduct( w->p[x], f2->plane->normal ) - f2->plane->dist;
|
|
|
|
if( dist >= -g_csgepsilon * 4 ) // only estimate
|
|
valid++;
|
|
}
|
|
|
|
if( valid >= 2 )
|
|
{
|
|
// this splitplane forms an edge
|
|
winding_t *fw, *bw;
|
|
|
|
ClipWindingEpsilon( f->w, f2->plane->normal, f2->plane->dist, g_csgepsilon, &fw, &bw );
|
|
|
|
if( fw )
|
|
{
|
|
bface_t *front = NewFaceFromFace( f );
|
|
front->w = fw;
|
|
WindingBounds( fw, front->mins, front->maxs );
|
|
front->next = outside;
|
|
outside = front;
|
|
}
|
|
|
|
if( bw )
|
|
{
|
|
FreeWinding( f->w );
|
|
WindingBounds( bw, f->mins, f->maxs );
|
|
f->w = bw;
|
|
}
|
|
else
|
|
{
|
|
FreeFace( f );
|
|
f = NULL;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
FreeWinding( w );
|
|
}
|
|
else
|
|
{
|
|
f->next = outside;
|
|
outside = f;
|
|
f = NULL;
|
|
}
|
|
|
|
area = f ? WindingArea( f->w ) : 0.0;
|
|
|
|
if( f && area <= 0.0 )
|
|
{
|
|
MsgDev( D_WARN, "Entity %i, Brush %i: tiny penetration\n", b1->originalentitynum, b1->originalbrushnum );
|
|
FreeFace( f );
|
|
f = NULL;
|
|
}
|
|
|
|
if( f )
|
|
{
|
|
// there is one convex fragment of the original
|
|
// face left inside brush2
|
|
if( b2->csg_detaillevel( hull ) > b1->csg_detaillevel( hull ))
|
|
{
|
|
// don't chop or set contents, only nullify
|
|
f->next = outside;
|
|
outside = f;
|
|
SetBits( f->flags, FSIDE_NODRAW );
|
|
f->texinfo = -1;
|
|
continue;
|
|
}
|
|
|
|
if( b2->csg_detaillevel( hull ) < b1->csg_detaillevel( hull ) && b2->contents == CONTENTS_SOLID )
|
|
{
|
|
// real solid
|
|
FreeFace( f );
|
|
continue;
|
|
}
|
|
|
|
if( b1->contents == CONTENTS_EMPTY )
|
|
{
|
|
bool onfront = true;
|
|
bool onback = true;
|
|
|
|
for( f2 = bh2->faces; f2 != NULL; f2 = f2->next )
|
|
{
|
|
if( f->planenum == ( f2->planenum ^ 1 ))
|
|
onback = false;
|
|
if( f->planenum == f2->planenum )
|
|
onfront = false;
|
|
}
|
|
|
|
if( onfront && f->contents[0] < b2->contents )
|
|
f->contents[0] = b2->contents;
|
|
|
|
if( onback && f->contents[1] < b2->contents )
|
|
f->contents[1] = b2->contents;
|
|
|
|
if( f->contents[0] == CONTENTS_SOLID && f->contents[1] == CONTENTS_SOLID && FBitSet( f->flags, FSIDE_SOLIDHINT ))
|
|
{
|
|
FreeFace( f );
|
|
}
|
|
else
|
|
{
|
|
f->next = outside;
|
|
outside = f;
|
|
}
|
|
continue;
|
|
}
|
|
|
|
if( b1->contents > b2->contents || ( b1->contents == b2->contents && FBitSet( f->flags, FSIDE_SOLIDHINT )))
|
|
{
|
|
// inside a water brush
|
|
f->contents[0] = b2->contents;
|
|
f->next = outside;
|
|
outside = f;
|
|
}
|
|
else
|
|
{
|
|
// inside a solid brush
|
|
FreeFace( f ); // throw it away
|
|
}
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
if( hull == HULL_VISIBLE )
|
|
WriteMapBrushes( b1, outside );
|
|
|
|
// all of the faces left in outside are real surface faces
|
|
SaveOutside( b1, hull, outside );
|
|
}
|
|
}
|
|
|
|
/*
|
|
==================
|
|
ChopEntityBrushes
|
|
|
|
Chop brushes for a gived entity
|
|
and dump result faces into text-format files
|
|
that called mapname.p0-3
|
|
==================
|
|
*/
|
|
void ChopEntityBrushes( mapent_t *mapent )
|
|
{
|
|
ASSERT( mapent->numbrushes > 0 );
|
|
|
|
// sort the contents down so stone bites water, etc
|
|
g_firstbrush = mapent->firstbrush;
|
|
|
|
// csg them in order
|
|
if( mapent == &g_mapentities[0] )
|
|
{
|
|
RunThreadsOnIndividual( mapent->numbrushes, true, CSGBrush );
|
|
}
|
|
else
|
|
{
|
|
// brushmodels use silent threads
|
|
RunThreadsOnIndividual( mapent->numbrushes, false, CSGBrush );
|
|
}
|
|
} |