This repository has been archived on 2022-06-27. You can view files and clone it, but cannot push or open issues or pull requests.
Xash3DArchive/common/bsplib/surface.c

695 lines
17 KiB
C

//=======================================================================
// Copyright XashXT Group 2007 ©
// surface.c - emit bsp surfaces
//=======================================================================
#include "bsplib.h"
#include "mathlib.h"
#include "const.h"
#define SNAP_FLOAT_TO_INT 32
#define SNAP_INT_TO_FLOAT (1.0 / SNAP_FLOAT_TO_INT)
#define COLINEAR_AREA 10
#define MAX_INDICES 1024
drawsurf_t drawsurfs[MAX_MAP_SURFACES];
int numdrawsurfs;
int c_stripSurfaces, c_fanSurfaces;
void ComputeAxisBase( vec3_t normal, vec3_t texX, vec3_t texY )
{
vec_t RotY, RotZ;
// do some cleaning
if( fabs( normal[0] ) < 1E-6 ) normal[0] = 0.0f;
if( fabs( normal[1] ) < 1E-6 ) normal[1] = 0.0f;
if( fabs( normal[2] ) < 1E-6 ) normal[2] = 0.0f;
// compute the two rotations around Y and Z to rotate X to normal
RotY = -atan2( normal[2], sqrt( normal[1] * normal[1] + normal[0] * normal[0] ));
RotZ = atan2( normal[1], normal[0] );
// rotate (0, 1, 0) and (0, 0, 1) to compute texX and texY
texX[0] = -sin( RotZ );
texX[1] = cos( RotZ );
texX[2] = 0;
// the texY vector is along -Z ( T texture coorinates axis )
texY[0] = -sin( RotY ) * cos( RotZ );
texY[1] = -sin( RotY ) * sin( RotZ );
texY[2] = -cos( RotY );
}
/*
==================
IsTriangleDegenerate
Returns qtrue if all three points are collinear or backwards
===================
*/
static bool IsTriangleDegenerate( dvertex_t *points, int a, int b, int c )
{
vec3_t v1, v2, v3;
float d;
VectorSubtract( points[b].point, points[a].point, v1 );
VectorSubtract( points[c].point, points[a].point, v2 );
CrossProduct( v1, v2, v3 );
d = VectorLength( v3 );
// assume all very small or backwards triangles will cause problems
if( d < COLINEAR_AREA )
return true;
return false;
}
/*
===============
SurfaceAsTriFan
The surface can't be represented as a single tristrip without
leaving a degenerate triangle (and therefore a crack), so add
a point in the middle and create (points-1) triangles in fan order
===============
*/
static void SurfaceAsTriFan( dsurface_t *ds )
{
int i;
vec4_t colorSum;
const float *colorIn;
dvertex_t *mid, *v;
// create a new point in the center of the face
if( numvertexes == MAX_MAP_VERTEXES ) Sys_Break( "MAX_MAP_VERTEXES limit exceeded\n" );
mid = &dvertexes[numvertexes];
numvertexes++;
colorSum[0] = colorSum[1] = colorSum[2] = colorSum[3] = 0;
v = dvertexes + ds->firstvertex;
for( i = 0; i < ds->numvertices; i++, v++ )
{
VectorAdd( mid->point, v->point, mid->point );
mid->st[0] += v->st[0];
mid->st[1] += v->st[1];
mid->lm[0] += v->lm[0];
mid->lm[1] += v->lm[1];
colorIn = UnpackRGBA( v->color );
colorSum[0] += colorIn[0];
colorSum[1] += colorIn[1];
colorSum[2] += colorIn[2];
colorSum[3] += colorIn[3];
}
mid->point[0] /= ds->numvertices;
mid->point[1] /= ds->numvertices;
mid->point[2] /= ds->numvertices;
mid->st[0] /= ds->numvertices;
mid->st[1] /= ds->numvertices;
mid->lm[0] /= ds->numvertices;
mid->lm[1] /= ds->numvertices;
colorSum[0] /= ds->numvertices;
colorSum[1] /= ds->numvertices;
colorSum[2] /= ds->numvertices;
colorSum[3] /= ds->numvertices;
mid->color = PackRGBA( colorSum[0], colorSum[1], colorSum[2], colorSum[3] );
VectorCopy( (dvertexes + ds->firstvertex)->normal, mid->normal );
// fill in indices in trifan order
if( numindexes + ds->numvertices * 3 > MAX_MAP_INDEXES )
Sys_Break( "MAX_MAP_INDEXES limit excceded\n" );
ds->firstindex = numindexes;
ds->numindices = ds->numvertices * 3;
for( i = 0 ; i < ds->numvertices; i++ )
{
dindexes[numindexes++] = ds->numvertices;
dindexes[numindexes++] = i;
dindexes[numindexes++] = (i+1) % ds->numvertices;
}
ds->numvertices++;
}
/*
================
SurfaceAsTristrip
Try to create indices that make (points-2) triangles in tristrip order
================
*/
static void SurfaceAsTristrip( dsurface_t *ds )
{
int i;
int rotate;
int numIndices;
int ni;
int a, b, c;
int indices[MAX_INDICES];
// determine the triangle strip order
numIndices = ( ds->numvertices - 2 ) * 3;
if( numIndices > MAX_INDICES ) Sys_Error( "MAX_INDICES limit exceeded for surface\n" );
// try all possible orderings of the points looking
// for a strip order that isn't degenerate
for( rotate = 0; rotate < ds->numvertices; rotate++ )
{
for( ni = 0, i = 0; i < ds->numvertices - 2 - i; i++ )
{
a = ( ds->numvertices - 1 - i + rotate ) % ds->numvertices;
b = ( i + rotate ) % ds->numvertices;
c = ( ds->numvertices - 2 - i + rotate ) % ds->numvertices;
if( IsTriangleDegenerate( dvertexes + ds->firstvertex, a, b, c ))
break;
indices[ni++] = a;
indices[ni++] = b;
indices[ni++] = c;
if( i + 1 != ds->numvertices - 1 - i )
{
a = ( ds->numvertices - 2 - i + rotate ) % ds->numvertices;
b = ( i + rotate ) % ds->numvertices;
c = ( i + 1 + rotate ) % ds->numvertices;
if( IsTriangleDegenerate( dvertexes + ds->firstvertex, a, b, c ))
break;
indices[ni++] = a;
indices[ni++] = b;
indices[ni++] = c;
}
}
if( ni == numIndices ) break; // got it done without degenerate triangles
}
// if any triangle in the strip is degenerate,
// render from a centered fan point instead
if( ni < numIndices )
{
c_fanSurfaces++;
SurfaceAsTriFan( ds );
return;
}
// a normal tristrip
c_stripSurfaces++;
if( numindexes + ni > MAX_MAP_INDEXES )
Sys_Break( "MAX_MAP_NDEXES limit exceeded\n" );
ds->firstindex = numindexes;
ds->numindices = ni;
Mem_Copy( dindexes + numindexes, indices, ni * sizeof( int ));
numindexes += ni;
}
/*
===============
EmitPlanarSurf
===============
*/
void EmitPlanarSurf( drawsurf_t *ds )
{
int j;
dsurface_t *out;
dvertex_t *outv;
if( numsurfaces == MAX_MAP_SURFACES ) Sys_Break( "MAX_MAP_SURFACES limit exceeded\n" );
out = &dsurfaces[numsurfaces];
numsurfaces++;
out->planenum = ds->planenum & (~1);
out->lm_side = ds->planenum & 1;
out->shadernum = EmitShader( ds->shader->name );
out->lightmapnum = ds->lightmapNum;
out->firstvertex = numvertexes;
out->numvertices = ds->numverts;
out->lm_base[0] = ds->lightmapX;
out->lm_base[1] = ds->lightmapY;
out->lm_size[0] = ds->lightmapWidth;
out->lm_size[1] = ds->lightmapHeight;
out->styles[0] = 255; // no lightstyles
VectorCopy( ds->lightmapOrigin, out->origin );
VectorCopy( ds->lightmapVecs[0], out->vecs[0] );
VectorCopy( ds->lightmapVecs[1], out->vecs[1] );
VectorCopy( ds->lightmapVecs[2], out->normal );
for( j = 0; j < ds->numverts; j++ )
{
if( numvertexes == MAX_MAP_VERTEXES )
Sys_Break( "MAX_MAP_VERTEXES limit exceeded\n" );
outv = &dvertexes[numvertexes];
numvertexes++;
Mem_Copy( outv, &ds->verts[j], sizeof( *outv ));
outv->color = PackRGBA( 1.0f, 1.0f, 1.0f, 1.0f );
}
// create the indexes
SurfaceAsTristrip( out );
}
/*
=============================================================================
DRAWSURF CONSTRUCTION
=============================================================================
*/
/*
=================
AllocDrawSurf
=================
*/
drawsurf_t *AllocDrawSurf( void )
{
drawsurf_t *ds;
if( numdrawsurfs >= MAX_MAP_SURFACES )
Sys_Break( "MAX_MAP_SURFACES limit exceeded\n");
ds = &drawsurfs[numdrawsurfs];
numdrawsurfs++;
return ds;
}
/*
=================
DrawSurfaceForSide
=================
*/
drawsurf_t *DrawSurfaceForSide( bspbrush_t *b, side_t *s, winding_t *w )
{
drawsurf_t *ds;
int i, j;
bsp_shader_t *si;
dvertex_t *dv;
float mins[2], maxs[2];
vec3_t texX,texY;
vec_t x,y;
if( w->numpoints > MAX_POINTS_ON_WINDING )
Sys_Error( "DrawSurfaceForSide: winding overflow\n" );
si = s->shader;
ds = AllocDrawSurf();
ds->shader = si;
ds->mapbrush = b;
ds->side = s;
ds->numverts = w->numpoints;
ds->verts = BSP_Malloc( ds->numverts * sizeof( *ds->verts ));
ds->planenum = s->planenum; // g-cont. probably unneeded
mins[0] = mins[1] = 99999;
maxs[0] = maxs[1] = -99999;
// compute s/t coordinates from brush primitive texture matrix
// compute axis base
ComputeAxisBase( mapplanes[s->planenum].normal, texX, texY );
for( j = 0; j < w->numpoints; j++ )
{
dv = ds->verts + j;
// round the xyz to a given precision
for( i = 0; i < 3; i++ )
dv->point[i] = SNAP_INT_TO_FLOAT * floor( w->p[j][i] * SNAP_FLOAT_TO_INT + 0.5 );
if( g_brushtype == BRUSH_RADIANT )
{
// calculate texture s/t from brush primitive texture matrix
x = DotProduct( dv->point, texX );
y = DotProduct( dv->point, texY );
dv->st[0] = s->matrix[0][0] * x + s->matrix[0][1] * y + s->matrix[0][2];
dv->st[1] = s->matrix[1][0] * x + s->matrix[1][1] * y + s->matrix[1][2];
}
else
{
// calculate texture s/t
dv->st[0] = s->vecs[0][3] + DotProduct( s->vecs[0], dv->point );
dv->st[1] = s->vecs[1][3] + DotProduct( s->vecs[1], dv->point );
dv->st[0] /= si->width;
dv->st[1] /= si->height;
}
for( i = 0; i < 2; i++ )
{
if( dv->st[i] < mins[i] ) mins[i] = dv->st[i];
if( dv->st[i] > maxs[i] ) maxs[i] = dv->st[i];
}
// copy normal
// g-cont. hey we can restore normals on a map loading
VectorCopy ( mapplanes[s->planenum].normal, dv->normal );
}
// adjust the texture coordinates to be as close to 0 as possible
if( !si->globalTexture )
{
mins[0] = floor( mins[0] );
mins[1] = floor( mins[1] );
for( i = 0; i < w->numpoints; i++ )
{
dv = ds->verts + i;
dv->st[0] -= mins[0];
dv->st[1] -= mins[1];
}
}
return ds;
}
/*
===================
SubdivideDrawSurf
===================
*/
void SubdivideDrawSurf( drawsurf_t *ds, winding_t *w, float subdivisions )
{
int i;
int axis;
vec3_t bounds[2];
const float epsilon = 0.1;
int subFloor, subCeil;
winding_t *frontWinding, *backWinding;
drawsurf_t *newds;
if( !w ) return;
if( w->numpoints < 3 ) Sys_Break( "SubdivideDrawSurf: too few w->numpoints\n" );
ClearBounds( bounds[0], bounds[1] );
for( i = 0; i < w->numpoints; i++ )
{
AddPointToBounds( w->p[i], bounds[0], bounds[1] );
}
for( axis = 0; axis < 3; axis++ )
{
vec3_t planePoint = { 0, 0, 0 };
vec3_t planeNormal = { 0, 0, 0 };
float d;
subFloor = floor( bounds[0][axis] / subdivisions ) * subdivisions;
subCeil = ceil( bounds[1][axis] / subdivisions ) * subdivisions;
planePoint[axis] = subFloor + subdivisions;
planeNormal[axis] = -1;
d = DotProduct( planePoint, planeNormal );
// subdivide if necessary
if( subCeil - subFloor > subdivisions )
{
// gotta clip polygon into two polygons
ClipWindingEpsilon( w, planeNormal, d, epsilon, &frontWinding, &backWinding );
// the clip may not produce two polygons if it was epsilon close
if( !frontWinding ) w = backWinding;
else if( !backWinding ) w = frontWinding;
else
{
SubdivideDrawSurf( ds, frontWinding, subdivisions );
SubdivideDrawSurf( ds, backWinding, subdivisions );
return;
}
}
}
// emit this polygon
newds = DrawSurfaceForSide( ds->mapbrush, ds->side, w );
}
/*
=====================
SubdivideDrawSurfs
Chop up surfaces that have subdivision attributes
=====================
*/
void SubdivideDrawSurfs( bsp_entity_t *e, tree_t *tree )
{
int i;
drawsurf_t *ds;
winding_t *w;
float subdivision;
int numbasedrawsurfs;
bsp_shader_t *si;
MsgDev( D_INFO, "----- Subdivide Surfaces -----\n" );
numbasedrawsurfs = numdrawsurfs;
for( i = e->firstsurf; i < numbasedrawsurfs; i++ )
{
ds = &drawsurfs[i];
// only subdivide brush sides, not patches or misc_models
if ( !ds->side ) {
continue;
}
// check subdivision for shader
si = ds->side->shader;
if( !si ) continue;
subdivision = si->subdivisions;
if( !subdivision ) continue;
w = WindingFromDrawSurf( ds );
ds->numverts = 0; // remove this reference
SubdivideDrawSurf( ds, w, subdivision );
}
}
//===================================================================================
/*
====================
ClipSideIntoTree_r
Adds non-opaque leaf fragments to the convex hull
====================
*/
void ClipSideIntoTree_r( winding_t *w, side_t *side, node_t *node )
{
plane_t *plane;
winding_t *front, *back;
if( !w ) return;
if( node->planenum != PLANENUM_LEAF )
{
if( side->planenum == node->planenum )
{
ClipSideIntoTree_r( w, side, node->children[0] );
return;
}
if( side->planenum == ( node->planenum ^ 1))
{
ClipSideIntoTree_r( w, side, node->children[1] );
return;
}
plane = &mapplanes[ node->planenum ];
ClipWindingEpsilon( w, plane->normal, plane->dist, ON_EPSILON, &front, &back );
FreeWinding( w );
ClipSideIntoTree_r( front, side, node->children[0] );
ClipSideIntoTree_r( back, side, node->children[1] );
return;
}
// if opaque leaf, don't add
if( !node->opaque )
{
AddWindingToConvexHull( w, &side->visibleHull, mapplanes[ side->planenum ].normal );
}
FreeWinding( w );
return;
}
/*
=====================
ClipSidesIntoTree
Creates side->visibleHull for all visible sides
The drawsurf for a side will consist of the convex hull of
all points in non-opaque clusters, which allows overlaps
to be trimmed off automatically.
=====================
*/
void ClipSidesIntoTree( bsp_entity_t *e, tree_t *tree )
{
bspbrush_t *b;
int i;
winding_t *w;
bsp_shader_t *si;
side_t *side, *newSide;
MsgDev( D_INFO, "----- ClipSidesIntoTree -----\n" );
for( b = e->brushes; b; b = b->next )
{
for( i = 0; i < b->numsides; i++ )
{
side = &b->sides[i];
if( !side->winding ) continue;
w = CopyWinding( side->winding );
side->visibleHull = NULL;
ClipSideIntoTree_r( w, side, tree->headnode );
w = side->visibleHull;
if( !w ) continue;
si = side->shader;
if( !si ) continue;
// don't create faces for non-visible sides
if( si->surfaceFlags & SURF_NODRAW ) continue;
if( side->bevel ) Sys_Break( "Sys_MonkeyShouldBeSpanked!\n" );
// save this winding as a visible surface
DrawSurfaceForSide( b, side, w );
// make a back side for it if needed
if( !(si->contents & CONTENTS_FOG ))
continue;
// duplicate the up-facing side
w = ReverseWinding( w );
newSide = BSP_Malloc( sizeof( *side ));
*newSide = *side;
newSide->visibleHull = w;
newSide->planenum ^= 1;
// save this winding as a visible surface
DrawSurfaceForSide( b, newSide, w );
}
}
}
/*
===================================================================================
FILTER REFERENCES DOWN THE TREE
===================================================================================
*/
/*
====================
FilterSideIntoTree_r
Place a reference to the given drawsurf in every leaf it contacts
====================
*/
int FilterSideIntoTree_r( winding_t *w, side_t *side, drawsurf_t *ds, node_t *node )
{
surfaceref_t *dsr;
plane_t *plane;
winding_t *front, *back;
int total;
if( !w ) return 0;
if( node->planenum != PLANENUM_LEAF )
{
if( side->planenum == node->planenum )
return FilterSideIntoTree_r( w, side, ds, node->children[0] );
if( side->planenum == ( node->planenum ^ 1 ))
return FilterSideIntoTree_r( w, side, ds, node->children[1] );
plane = &mapplanes[node->planenum];
ClipWindingEpsilon( w, plane->normal, plane->dist, ON_EPSILON, &front, &back );
total = FilterSideIntoTree_r( front, side, ds, node->children[0] );
total += FilterSideIntoTree_r( back, side, ds, node->children[1] );
FreeWinding( w );
return total;
}
// if opaque leaf, don't add
if( node->opaque ) return 0;
dsr = BSP_Malloc( sizeof( *dsr ));
dsr->outputnum = numsurfaces;
dsr->next = node->surfaces;
node->surfaces = dsr;
FreeWinding( w );
return 1;
}
/*
=====================
FilterFaceIntoTree
=====================
*/
int FilterFaceIntoTree( drawsurf_t *ds, tree_t *tree )
{
winding_t *w;
int l;
w = WindingFromDrawSurf( ds );
l = FilterSideIntoTree_r( w, ds->side, ds, tree->headnode );
return l;
}
/*
=====================
FilterDrawsurfsIntoTree
Upon completion, all drawsurfs that actually generate a reference
will have been emited to the bspfile arrays, and the references
will have valid final indexes
=====================
*/
void FilterDrawsurfsIntoTree( bsp_entity_t *e, tree_t *tree )
{
int i;
drawsurf_t *ds;
int refs;
int c_surfs = 0, c_refs = 0;
MsgDev( D_INFO, "----- FilterDrawsurfsIntoTree -----\n" );
c_stripSurfaces = c_fanSurfaces = 0;
for( i = e->firstsurf; i < numdrawsurfs; i++ )
{
ds = &drawsurfs[i];
if( !ds->numverts ) continue;
refs = FilterFaceIntoTree( ds, tree );
EmitPlanarSurf( ds );
if( refs > 0 )
{
c_surfs++;
c_refs += refs;
}
}
MsgDev( D_INFO, "%5i emited drawsurfs\n", c_surfs );
MsgDev( D_INFO, "%5i references\n", c_refs );
MsgDev( D_INFO, "%5i stripfaces\n", c_stripSurfaces );
MsgDev( D_INFO, "%5i fanfaces\n", c_fanSurfaces );
}