Paranoia2/utils/p2csg/csg4.cpp
2020-08-31 19:50:41 +03:00

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 );
}
}