netradiant-custom/tools/quake3/q3map2/surface.c
Lauri Kasanen 123f3fd962 q3map2: Small cleanups
Move one variable to the section it is used in, and remove one unnecessary
NULL check.

If si were NULL at that point, we would have segfaulted ages ago.

Signed-off-by: Lauri Kasanen <curaga@operamail.com>
2012-03-03 17:28:34 +01:00

3682 lines
91 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 SURFACE_C
/* dependencies */
#include "q3map2.h"
/*
AllocDrawSurface()
ydnar: gs mods: changed to force an explicit type when allocating
*/
mapDrawSurface_t *AllocDrawSurface( surfaceType_t type )
{
mapDrawSurface_t *ds;
/* ydnar: gs mods: only allocate valid types */
if( type <= SURFACE_BAD || type >= NUM_SURFACE_TYPES )
Error( "AllocDrawSurface: Invalid surface type %d specified", type );
/* bounds check */
if( numMapDrawSurfs >= MAX_MAP_DRAW_SURFS )
Error( "MAX_MAP_DRAW_SURFS (%d) exceeded", MAX_MAP_DRAW_SURFS );
ds = &mapDrawSurfs[ numMapDrawSurfs ];
numMapDrawSurfs++;
/* ydnar: do initial surface setup */
memset( ds, 0, sizeof( mapDrawSurface_t ) );
ds->type = type;
ds->planeNum = -1;
ds->fogNum = defaultFogNum; /* ydnar 2003-02-12 */
ds->outputNum = -1; /* ydnar 2002-08-13 */
ds->surfaceNum = numMapDrawSurfs - 1; /* ydnar 2003-02-16 */
return ds;
}
/*
FinishSurface()
ydnar: general surface finish pass
*/
void FinishSurface( mapDrawSurface_t *ds )
{
mapDrawSurface_t *ds2;
/* dummy check */
if( ds->type <= SURFACE_BAD || ds->type >= NUM_SURFACE_TYPES || ds == NULL || ds->shaderInfo == NULL )
return;
/* ydnar: rocking tek-fu celshading */
if( ds->celShader != NULL )
MakeCelSurface( ds, ds->celShader );
/* backsides stop here */
if( ds->backSide )
return;
/* ydnar: rocking surface cloning (fur baby yeah!) */
if( ds->shaderInfo->cloneShader != NULL && ds->shaderInfo->cloneShader[ 0 ] != '\0' )
CloneSurface( ds, ShaderInfoForShader( ds->shaderInfo->cloneShader ) );
/* ydnar: q3map_backShader support */
if( ds->shaderInfo->backShader != NULL && ds->shaderInfo->backShader[ 0 ] != '\0' )
{
ds2 = CloneSurface( ds, ShaderInfoForShader( ds->shaderInfo->backShader ) );
ds2->backSide = qtrue;
}
}
/*
CloneSurface()
clones a map drawsurface, using the specified shader
*/
mapDrawSurface_t *CloneSurface( mapDrawSurface_t *src, shaderInfo_t *si )
{
mapDrawSurface_t *ds;
/* dummy check */
if( src == NULL || si == NULL )
return NULL;
/* allocate a new surface */
ds = AllocDrawSurface( src->type );
if( ds == NULL )
return NULL;
/* copy it */
memcpy( ds, src, sizeof( *ds ) );
/* destroy side reference */
ds->sideRef = NULL;
/* set shader */
ds->shaderInfo = si;
/* copy verts */
if( ds->numVerts > 0 )
{
ds->verts = safe_malloc( ds->numVerts * sizeof( *ds->verts ) );
memcpy( ds->verts, src->verts, ds->numVerts * sizeof( *ds->verts ) );
}
/* copy indexes */
if( ds->numIndexes <= 0 )
return ds;
ds->indexes = safe_malloc( ds->numIndexes * sizeof( *ds->indexes ) );
memcpy( ds->indexes, src->indexes, ds->numIndexes * sizeof( *ds->indexes ) );
/* return the surface */
return ds;
}
/*
MakeCelSurface() - ydnar
makes a copy of a surface, but specific to cel shading
*/
mapDrawSurface_t *MakeCelSurface( mapDrawSurface_t *src, shaderInfo_t *si )
{
mapDrawSurface_t *ds;
/* dummy check */
if( src == NULL || si == NULL )
return NULL;
/* don't create cel surfaces for certain types of shaders */
if( (src->shaderInfo->compileFlags & C_TRANSLUCENT) ||
(src->shaderInfo->compileFlags & C_SKY) )
return NULL;
/* make a copy */
ds = CloneSurface( src, si );
if( ds == NULL )
return NULL;
/* do some fixups for celshading */
ds->planar = qfalse;
ds->planeNum = -1;
ds->celShader = NULL; /* don't cel shade cels :P */
/* return the surface */
return ds;
}
/*
MakeSkyboxSurface() - ydnar
generates a skybox surface, viewable from everywhere there is sky
*/
mapDrawSurface_t *MakeSkyboxSurface( mapDrawSurface_t *src )
{
int i;
mapDrawSurface_t *ds;
/* dummy check */
if( src == NULL )
return NULL;
/* make a copy */
ds = CloneSurface( src, src->shaderInfo );
if( ds == NULL )
return NULL;
/* set parent */
ds->parent = src;
/* scale the surface vertexes */
for( i = 0; i < ds->numVerts; i++ )
{
m4x4_transform_point( skyboxTransform, ds->verts[ i ].xyz );
/* debug code */
//% bspDrawVerts[ bspDrawSurfaces[ ds->outputNum ].firstVert + i ].color[ 0 ][ 1 ] = 0;
//% bspDrawVerts[ bspDrawSurfaces[ ds->outputNum ].firstVert + i ].color[ 0 ][ 2 ] = 0;
}
/* so backface culling creep doesn't bork the surface */
VectorClear( ds->lightmapVecs[ 2 ] );
/* return the surface */
return ds;
}
/*
IsTriangleDegenerate
returns qtrue if all three points are colinear, backwards, or the triangle is just plain bogus
*/
#define TINY_AREA 1.0f
qboolean IsTriangleDegenerate( bspDrawVert_t *points, int a, int b, int c )
{
vec3_t v1, v2, v3;
float d;
/* calcuate the area of the triangle */
VectorSubtract( points[ b ].xyz, points[ a ].xyz, v1 );
VectorSubtract( points[ c ].xyz, points[ a ].xyz, v2 );
CrossProduct( v1, v2, v3 );
d = VectorLength( v3 );
/* assume all very small or backwards triangles will cause problems */
if( d < TINY_AREA )
return qtrue;
/* must be a good triangle */
return qfalse;
}
/*
ClearSurface() - ydnar
clears a surface and frees any allocated memory
*/
void ClearSurface( mapDrawSurface_t *ds )
{
ds->type = SURFACE_BAD;
ds->planar = qfalse;
ds->planeNum = -1;
ds->numVerts = 0;
if( ds->verts != NULL )
free( ds->verts );
ds->verts = NULL;
ds->numIndexes = 0;
if( ds->indexes != NULL )
free( ds->indexes );
ds->indexes = NULL;
numClearedSurfaces++;
}
/*
TidyEntitySurfaces() - ydnar
deletes all empty or bad surfaces from the surface list
*/
void TidyEntitySurfaces( entity_t *e )
{
int i, j, deleted;
mapDrawSurface_t *out, *in = NULL;
/* note it */
Sys_FPrintf( SYS_VRB, "--- TidyEntitySurfaces ---\n" );
/* walk the surface list */
deleted = 0;
for( i = e->firstDrawSurf, j = e->firstDrawSurf; j < numMapDrawSurfs; i++, j++ )
{
/* get out surface */
out = &mapDrawSurfs[ i ];
/* walk the surface list again until a proper surface is found */
for( ; j < numMapDrawSurfs; j++ )
{
/* get in surface */
in = &mapDrawSurfs[ j ];
/* this surface ok? */
if( in->type == SURFACE_FLARE || in->type == SURFACE_SHADER ||
(in->type != SURFACE_BAD && in->numVerts > 0) )
break;
/* nuke it */
ClearSurface( in );
deleted++;
}
/* copy if necessary */
if( i != j )
memcpy( out, in, sizeof( mapDrawSurface_t ) );
}
/* set the new number of drawsurfs */
numMapDrawSurfs = i;
/* emit some stats */
Sys_FPrintf( SYS_VRB, "%9d empty or malformed surfaces deleted\n", deleted );
}
/*
CalcSurfaceTextureRange() - ydnar
calculates the clamped texture range for a given surface, returns qtrue if it's within [-texRange,texRange]
*/
qboolean CalcSurfaceTextureRange( mapDrawSurface_t *ds )
{
int i, j, v, size[ 2 ];
float mins[ 2 ], maxs[ 2 ];
/* try to early out */
if( ds->numVerts <= 0 )
return qtrue;
/* walk the verts and determine min/max st values */
mins[ 0 ] = 999999;
mins[ 1 ] = 999999;
maxs[ 0 ] = -999999;
maxs[ 1 ] = -999999;
for( i = 0; i < ds->numVerts; i++ )
{
for( j = 0; j < 2; j++ )
{
if( ds->verts[ i ].st[ j ] < mins[ j ] )
mins[ j ] = ds->verts[ i ].st[ j ];
if( ds->verts[ i ].st[ j ] > maxs[ j ] )
maxs[ j ] = ds->verts[ i ].st[ j ];
}
}
/* clamp to integer range and calculate surface bias values */
for( j = 0; j < 2; j++ )
ds->bias[ j ] = -floor( 0.5f * (mins[ j ] + maxs[ j ]) );
/* find biased texture coordinate mins/maxs */
size[ 0 ] = ds->shaderInfo->shaderWidth;
size[ 1 ] = ds->shaderInfo->shaderHeight;
ds->texMins[ 0 ] = 999999;
ds->texMins[ 1 ] = 999999;
ds->texMaxs[ 0 ] = -999999;
ds->texMaxs[ 1 ] = -999999;
for( i = 0; i < ds->numVerts; i++ )
{
for( j = 0; j < 2; j++ )
{
v = ((float) ds->verts[ i ].st[ j ] + ds->bias[ j ]) * size[ j ];
if( v < ds->texMins[ j ] )
ds->texMins[ j ] = v;
if( v > ds->texMaxs[ j ] )
ds->texMaxs[ j ] = v;
}
}
/* calc ranges */
for( j = 0; j < 2; j++ )
ds->texRange[ j ] = (ds->texMaxs[ j ] - ds->texMins[ j ]);
/* if range is zero, then assume unlimited precision */
if( texRange == 0 )
return qtrue;
/* within range? */
for( j = 0; j < 2; j++ )
{
if( ds->texMins[ j ] < -texRange || ds->texMaxs[ j ] > texRange )
return qfalse;
}
/* within range */
return qtrue;
}
/*
CalcLightmapAxis() - ydnar
gives closed lightmap axis for a plane normal
*/
qboolean CalcLightmapAxis( vec3_t normal, vec3_t axis )
{
vec3_t absolute;
/* test */
if( normal[ 0 ] == 0.0f && normal[ 1 ] == 0.0f && normal[ 2 ] == 0.0f )
{
VectorClear( axis );
return qfalse;
}
/* get absolute normal */
absolute[ 0 ] = fabs( normal[ 0 ] );
absolute[ 1 ] = fabs( normal[ 1 ] );
absolute[ 2 ] = fabs( normal[ 2 ] );
/* test and set */
if( absolute[ 2 ] > absolute[ 0 ] - 0.0001f && absolute[ 2 ] > absolute[ 1 ] - 0.0001f )
{
if( normal[ 2 ] > 0.0f )
VectorSet( axis, 0.0f, 0.0f, 1.0f );
else
VectorSet( axis, 0.0f, 0.0f, -1.0f );
}
else if( absolute[ 0 ] > absolute[ 1 ] - 0.0001f && absolute[ 0 ] > absolute[ 2 ] - 0.0001f )
{
if( normal[ 0 ] > 0.0f )
VectorSet( axis, 1.0f, 0.0f, 0.0f );
else
VectorSet( axis, -1.0f, 0.0f, 0.0f );
}
else
{
if( normal[ 1 ] > 0.0f )
VectorSet( axis, 0.0f, 1.0f, 0.0f );
else
VectorSet( axis, 0.0f, -1.0f, 0.0f );
}
/* return ok */
return qtrue;
}
/*
ClassifySurfaces() - ydnar
fills out a bunch of info in the surfaces, including planar status, lightmap projection, and bounding box
*/
#define PLANAR_EPSILON 0.5f //% 0.126f 0.25f
void ClassifySurfaces( int numSurfs, mapDrawSurface_t *ds )
{
int i, bestAxis;
float dist;
vec4_t plane;
shaderInfo_t *si;
static vec3_t axii[ 6 ] =
{
{ 0, 0, -1 },
{ 0, 0, 1 },
{ -1, 0, 0 },
{ 1, 0, 0 },
{ 0, -1, 0 },
{ 0, 1, 0 }
};
/* walk the list of surfaces */
for( ; numSurfs > 0; numSurfs--, ds++ )
{
/* ignore bogus (or flare) surfaces */
if( ds->type == SURFACE_BAD || ds->numVerts <= 0 )
continue;
/* get shader */
si = ds->shaderInfo;
/* -----------------------------------------------------------------
force meta if vertex count is too high or shader requires it
----------------------------------------------------------------- */
if( ds->type != SURFACE_PATCH && ds->type != SURFACE_FACE )
{
if( ds->numVerts > SHADER_MAX_VERTEXES )
ds->type = SURFACE_FORCED_META;
}
/* -----------------------------------------------------------------
plane and bounding box classification
----------------------------------------------------------------- */
/* set surface bounding box */
ClearBounds( ds->mins, ds->maxs );
for( i = 0; i < ds->numVerts; i++ )
AddPointToBounds( ds->verts[ i ].xyz, ds->mins, ds->maxs );
/* try to get an existing plane */
if( ds->planeNum >= 0 )
{
VectorCopy( mapplanes[ ds->planeNum ].normal, plane );
plane[ 3 ] = mapplanes[ ds->planeNum ].dist;
}
/* construct one from the first vert with a valid normal */
else
{
VectorClear( plane );
plane[ 3 ] = 0.0f;
for( i = 0; i < ds->numVerts; i++ )
{
if( ds->verts[ i ].normal[ 0 ] != 0.0f && ds->verts[ i ].normal[ 1 ] != 0.0f && ds->verts[ i ].normal[ 2 ] != 0.0f )
{
VectorCopy( ds->verts[ i ].normal, plane );
plane[ 3 ] = DotProduct( ds->verts[ i ].xyz, plane );
break;
}
}
}
/* test for bogus plane */
if( VectorLength( plane ) <= 0.0f )
{
ds->planar = qfalse;
ds->planeNum = -1;
}
else
{
/* determine if surface is planar */
ds->planar = qtrue;
/* test each vert */
for( i = 0; i < ds->numVerts; i++ )
{
/* point-plane test */
dist = DotProduct( ds->verts[ i ].xyz, plane ) - plane[ 3 ];
if( fabs( dist ) > PLANAR_EPSILON )
{
//% if( ds->planeNum >= 0 )
//% {
//% Sys_Printf( "WARNING: Planar surface marked unplanar (%f > %f)\n", fabs( dist ), PLANAR_EPSILON );
//% ds->verts[ i ].color[ 0 ][ 0 ] = ds->verts[ i ].color[ 0 ][ 2 ] = 0;
//% }
ds->planar = qfalse;
break;
}
}
}
/* find map plane if necessary */
if( ds->planar )
{
if( ds->planeNum < 0 )
ds->planeNum = FindFloatPlane( plane, plane[ 3 ], 1, &ds->verts[ 0 ].xyz );
VectorCopy( plane, ds->lightmapVecs[ 2 ] );
}
else
{
ds->planeNum = -1;
VectorClear( ds->lightmapVecs[ 2 ] );
//% if( ds->type == SURF_META || ds->type == SURF_FACE )
//% Sys_Printf( "WARNING: Non-planar face (%d): %s\n", ds->planeNum, ds->shaderInfo->shader );
}
/* -----------------------------------------------------------------
lightmap bounds and axis projection
----------------------------------------------------------------- */
/* vertex lit surfaces don't need this information */
if( si->compileFlags & C_VERTEXLIT || ds->type == SURFACE_TRIANGLES )
{
VectorClear( ds->lightmapAxis );
//% VectorClear( ds->lightmapVecs[ 2 ] );
ds->sampleSize = 0;
continue;
}
/* the shader can specify an explicit lightmap axis */
if( si->lightmapAxis[ 0 ] || si->lightmapAxis[ 1 ] || si->lightmapAxis[ 2 ] )
VectorCopy( si->lightmapAxis, ds->lightmapAxis );
else if( ds->type == SURFACE_FORCED_META )
VectorClear( ds->lightmapAxis );
else if( ds->planar )
CalcLightmapAxis( plane, ds->lightmapAxis );
else
{
/* find best lightmap axis */
for( bestAxis = 0; bestAxis < 6; bestAxis++ )
{
for( i = 0; i < ds->numVerts && bestAxis < 6; i++ )
{
//% Sys_Printf( "Comparing %1.3f %1.3f %1.3f to %1.3f %1.3f %1.3f\n",
//% ds->verts[ i ].normal[ 0 ], ds->verts[ i ].normal[ 1 ], ds->verts[ i ].normal[ 2 ],
//% axii[ bestAxis ][ 0 ], axii[ bestAxis ][ 1 ], axii[ bestAxis ][ 2 ] );
if( DotProduct( ds->verts[ i ].normal, axii[ bestAxis ] ) < 0.25f ) /* fixme: adjust this tolerance to taste */
break;
}
if( i == ds->numVerts )
break;
}
/* set axis if possible */
if( bestAxis < 6 )
{
//% if( ds->type == SURFACE_PATCH )
//% Sys_Printf( "Mapped axis %d onto patch\n", bestAxis );
VectorCopy( axii[ bestAxis ], ds->lightmapAxis );
}
/* debug code */
//% if( ds->type == SURFACE_PATCH )
//% Sys_Printf( "Failed to map axis %d onto patch\n", bestAxis );
}
/* calculate lightmap sample size */
if( ds->shaderInfo->lightmapSampleSize > 0 ) /* shader value overrides every other */
ds->sampleSize = ds->shaderInfo->lightmapSampleSize;
else if( ds->sampleSize <= 0 ) /* may contain the entity asigned value */
ds->sampleSize = sampleSize; /* otherwise use global default */
if( ds->lightmapScale > 0.0f ) /* apply surface lightmap scaling factor */
{
ds->sampleSize = ds->lightmapScale * (float)ds->sampleSize;
ds->lightmapScale = 0; /* applied */
}
if( ds->sampleSize < minSampleSize )
ds->sampleSize = minSampleSize;
if( ds->sampleSize < 1 )
ds->sampleSize = 1;
if( ds->sampleSize > 16384 ) /* powers of 2 are preferred */
ds->sampleSize = 16384;
}
}
/*
ClassifyEntitySurfaces() - ydnar
classifies all surfaces in an entity
*/
void ClassifyEntitySurfaces( entity_t *e )
{
int i;
/* note it */
Sys_FPrintf( SYS_VRB, "--- ClassifyEntitySurfaces ---\n" );
/* walk the surface list */
for( i = e->firstDrawSurf; i < numMapDrawSurfs; i++ )
{
FinishSurface( &mapDrawSurfs[ i ] );
ClassifySurfaces( 1, &mapDrawSurfs[ i ] );
}
/* tidy things up */
TidyEntitySurfaces( e );
}
/*
GetShaderIndexForPoint() - ydnar
for shader-indexed surfaces (terrain), find a matching index from the indexmap
*/
byte GetShaderIndexForPoint( indexMap_t *im, vec3_t eMins, vec3_t eMaxs, vec3_t point )
{
int i, x, y;
float s, t;
vec3_t mins, maxs, size;
/* early out if no indexmap */
if( im == NULL )
return 0;
/* this code is really broken */
#if 0
/* legacy precision fudges for terrain */
for( i = 0; i < 3; i++ )
{
mins[ i ] = floor( eMins[ i ] + 0.1 );
maxs[ i ] = floor( eMaxs[ i ] + 0.1 );
size[ i ] = maxs[ i ] - mins[ i ];
}
/* find st (fixme: support more than just z-axis projection) */
s = floor( point[ 0 ] + 0.1f - mins[ 0 ] ) / size[ 0 ];
t = floor( maxs[ 1 ] - point[ 1 ] + 0.1f ) / size[ 1 ];
if( s < 0.0f )
s = 0.0f;
else if( s > 1.0f )
s = 1.0f;
if( t < 0.0f )
t = 0.0f;
else if( t > 1.0f )
t = 1.0f;
/* make xy */
x = (im->w - 1) * s;
y = (im->h - 1) * t;
#else
/* get size */
for( i = 0; i < 3; i++ )
{
mins[ i ] = eMins[ i ];
maxs[ i ] = eMaxs[ i ];
size[ i ] = maxs[ i ] - mins[ i ];
}
/* calc st */
s = (point[ 0 ] - mins[ 0 ]) / size[ 0 ];
t = (maxs[ 1 ] - point[ 1 ]) / size[ 1 ];
/* calc xy */
x = s * im->w;
y = t * im->h;
if( x < 0 )
x = 0;
else if( x > (im->w - 1) )
x = (im->w - 1);
if( y < 0 )
y = 0;
else if( y > (im->h - 1) )
y = (im->h - 1);
#endif
/* return index */
return im->pixels[ y * im->w + x ];
}
/*
GetIndexedShader() - ydnar
for a given set of indexes and an indexmap, get a shader and set the vertex alpha in-place
this combines a couple different functions from terrain.c
*/
shaderInfo_t *GetIndexedShader( shaderInfo_t *parent, indexMap_t *im, int numPoints, byte *shaderIndexes )
{
int i;
byte minShaderIndex, maxShaderIndex;
char shader[ MAX_QPATH ];
shaderInfo_t *si;
/* early out if bad data */
if( im == NULL || numPoints <= 0 || shaderIndexes == NULL )
return ShaderInfoForShader( "default" );
/* determine min/max index */
minShaderIndex = 255;
maxShaderIndex = 0;
for( i = 0; i < numPoints; i++ )
{
if( shaderIndexes[ i ] < minShaderIndex )
minShaderIndex = shaderIndexes[ i ];
if( shaderIndexes[ i ] > maxShaderIndex )
maxShaderIndex = shaderIndexes[ i ];
}
/* set alpha inline */
for( i = 0; i < numPoints; i++ )
{
/* straight rip from terrain.c */
if( shaderIndexes[ i ] < maxShaderIndex )
shaderIndexes[ i ] = 0;
else
shaderIndexes[ i ] = 255;
}
/* make a shader name */
if( minShaderIndex == maxShaderIndex )
sprintf( shader, "textures/%s_%d", im->shader, maxShaderIndex );
else
sprintf( shader, "textures/%s_%dto%d", im->shader, minShaderIndex, maxShaderIndex );
/* get the shader */
si = ShaderInfoForShader( shader );
/* inherit a few things from parent shader */
if( parent->globalTexture )
si->globalTexture = qtrue;
if( parent->forceMeta )
si->forceMeta = qtrue;
if( parent->nonplanar )
si->nonplanar = qtrue;
if( si->shadeAngleDegrees == 0.0 )
si->shadeAngleDegrees = parent->shadeAngleDegrees;
if( parent->tcGen && si->tcGen == qfalse )
{
/* set xy texture projection */
si->tcGen = qtrue;
VectorCopy( parent->vecs[ 0 ], si->vecs[ 0 ] );
VectorCopy( parent->vecs[ 1 ], si->vecs[ 1 ] );
}
if( VectorLength( parent->lightmapAxis ) > 0.0f && VectorLength( si->lightmapAxis ) <= 0.0f )
{
/* set lightmap projection axis */
VectorCopy( parent->lightmapAxis, si->lightmapAxis );
}
/* return the shader */
return si;
}
/*
DrawSurfaceForSide()
creates a SURF_FACE drawsurface from a given brush side and winding
*/
#define SNAP_FLOAT_TO_INT 8
#define SNAP_INT_TO_FLOAT (1.0 / SNAP_FLOAT_TO_INT)
mapDrawSurface_t *DrawSurfaceForSide( entity_t *e, brush_t *b, side_t *s, winding_t *w )
{
int i, j, k;
mapDrawSurface_t *ds;
shaderInfo_t *si, *parent;
bspDrawVert_t *dv;
vec3_t texX, texY;
vec_t x, y;
vec3_t vTranslated;
qboolean indexed;
byte shaderIndexes[ 256 ];
float offsets[ 256 ];
char tempShader[ MAX_QPATH ];
/* ydnar: don't make a drawsurf for culled sides */
if( s->culled )
return NULL;
/* range check */
if( w->numpoints > MAX_POINTS_ON_WINDING )
Error( "DrawSurfaceForSide: w->numpoints = %d (> %d)", w->numpoints, MAX_POINTS_ON_WINDING );
/* get shader */
si = s->shaderInfo;
/* ydnar: gs mods: check for indexed shader */
if( si->indexed && b->im != NULL )
{
/* indexed */
indexed = qtrue;
/* get shader indexes for each point */
for( i = 0; i < w->numpoints; i++ )
{
shaderIndexes[ i ] = GetShaderIndexForPoint( b->im, b->eMins, b->eMaxs, w->p[ i ] );
offsets[ i ] = b->im->offsets[ shaderIndexes[ i ] ];
//% Sys_Printf( "%f ", offsets[ i ] );
}
/* get matching shader and set alpha */
parent = si;
si = GetIndexedShader( parent, b->im, w->numpoints, shaderIndexes );
}
else
indexed = qfalse;
/* ydnar: sky hack/fix for GL_CLAMP borders on ati cards */
if( skyFixHack && si->skyParmsImageBase[ 0 ] != '\0' )
{
//% Sys_FPrintf( SYS_VRB, "Enabling sky hack for shader %s using env %s\n", si->shader, si->skyParmsImageBase );
sprintf( tempShader, "%s_lf", si->skyParmsImageBase );
DrawSurfaceForShader( tempShader );
sprintf( tempShader, "%s_rt", si->skyParmsImageBase );
DrawSurfaceForShader( tempShader );
sprintf( tempShader, "%s_ft", si->skyParmsImageBase );
DrawSurfaceForShader( tempShader );
sprintf( tempShader, "%s_bk", si->skyParmsImageBase );
DrawSurfaceForShader( tempShader );
sprintf( tempShader, "%s_up", si->skyParmsImageBase );
DrawSurfaceForShader( tempShader );
sprintf( tempShader, "%s_dn", si->skyParmsImageBase );
DrawSurfaceForShader( tempShader );
}
/* ydnar: gs mods */
ds = AllocDrawSurface( SURFACE_FACE );
ds->entityNum = b->entityNum;
ds->castShadows = b->castShadows;
ds->recvShadows = b->recvShadows;
ds->planar = qtrue;
ds->planeNum = s->planenum;
VectorCopy( mapplanes[ s->planenum ].normal, ds->lightmapVecs[ 2 ] );
ds->shaderInfo = si;
ds->mapBrush = b;
ds->sideRef = AllocSideRef( s, NULL );
ds->fogNum = -1;
ds->sampleSize = b->lightmapSampleSize;
ds->lightmapScale = b->lightmapScale;
ds->numVerts = w->numpoints;
ds->verts = safe_malloc( ds->numVerts * sizeof( *ds->verts ) );
memset( ds->verts, 0, ds->numVerts * sizeof( *ds->verts ) );
/* compute s/t coordinates from brush primitive texture matrix (compute axis base) */
ComputeAxisBase( mapplanes[ s->planenum ].normal, texX, texY );
/* create the vertexes */
for( j = 0; j < w->numpoints; j++ )
{
/* get the drawvert */
dv = ds->verts + j;
/* copy xyz and do potential z offset */
VectorCopy( w->p[ j ], dv->xyz );
if( indexed )
dv->xyz[ 2 ] += offsets[ j ];
/* round the xyz to a given precision and translate by origin */
for( i = 0 ; i < 3 ; i++ )
dv->xyz[ i ] = SNAP_INT_TO_FLOAT * floor( dv->xyz[ i ] * SNAP_FLOAT_TO_INT + 0.5f );
VectorAdd( dv->xyz, e->origin, vTranslated );
/* ydnar: tek-fu celshading support for flat shaded shit */
if( flat )
{
dv->st[ 0 ] = si->stFlat[ 0 ];
dv->st[ 1 ] = si->stFlat[ 1 ];
}
/* ydnar: gs mods: added support for explicit shader texcoord generation */
else if( si->tcGen )
{
dv->st[ 0 ] = DotProduct( si->vecs[ 0 ], vTranslated );
dv->st[ 1 ] = DotProduct( si->vecs[ 1 ], vTranslated );
}
/* old quake-style texturing */
else if( g_bBrushPrimit == BPRIMIT_OLDBRUSHES )
{
/* nearest-axial projection */
dv->st[ 0 ] = s->vecs[ 0 ][ 3 ] + DotProduct( s->vecs[ 0 ], vTranslated );
dv->st[ 1 ] = s->vecs[ 1 ][ 3 ] + DotProduct( s->vecs[ 1 ], vTranslated );
dv->st[ 0 ] /= si->shaderWidth;
dv->st[ 1 ] /= si->shaderHeight;
}
/* brush primitive texturing */
else
{
/* calculate texture s/t from brush primitive texture matrix */
x = DotProduct( vTranslated, texX );
y = DotProduct( vTranslated, texY );
dv->st[ 0 ] = s->texMat[ 0 ][ 0 ] * x + s->texMat[ 0 ][ 1 ] * y + s->texMat[ 0 ][ 2 ];
dv->st[ 1 ] = s->texMat[ 1 ][ 0 ] * x + s->texMat[ 1 ][ 1 ] * y + s->texMat[ 1 ][ 2 ];
}
/* copy normal */
VectorCopy( mapplanes[ s->planenum ].normal, dv->normal );
/* ydnar: set color */
for( k = 0; k < MAX_LIGHTMAPS; k++ )
{
dv->color[ k ][ 0 ] = 255;
dv->color[ k ][ 1 ] = 255;
dv->color[ k ][ 2 ] = 255;
/* ydnar: gs mods: handle indexed shader blending */
dv->color[ k ][ 3 ] = (indexed ? shaderIndexes[ j ] : 255);
}
}
/* set cel shader */
ds->celShader = b->celShader;
/* set shade angle */
if( b->shadeAngleDegrees > 0.0f )
ds->shadeAngleDegrees = b->shadeAngleDegrees;
/* ydnar: gs mods: moved st biasing elsewhere */
return ds;
}
/*
DrawSurfaceForMesh()
moved here from patch.c
*/
#define YDNAR_NORMAL_EPSILON 0.50f
qboolean VectorCompareExt( vec3_t n1, vec3_t n2, float epsilon )
{
int i;
/* test */
for( i= 0; i < 3; i++ )
if( fabs( n1[ i ] - n2[ i ]) > epsilon )
return qfalse;
return qtrue;
}
mapDrawSurface_t *DrawSurfaceForMesh( entity_t *e, parseMesh_t *p, mesh_t *mesh )
{
int i, k, numVerts;
vec4_t plane;
qboolean planar;
float dist;
mapDrawSurface_t *ds;
shaderInfo_t *si, *parent;
bspDrawVert_t *dv;
vec3_t vTranslated;
mesh_t *copy;
qboolean indexed;
byte shaderIndexes[ MAX_EXPANDED_AXIS * MAX_EXPANDED_AXIS ];
float offsets[ MAX_EXPANDED_AXIS * MAX_EXPANDED_AXIS ];
/* get mesh and shader shader */
if( mesh == NULL )
mesh = &p->mesh;
si = p->shaderInfo;
if( mesh == NULL || si == NULL )
return NULL;
/* get vertex count */
numVerts = mesh->width * mesh->height;
/* to make valid normals for patches with degenerate edges,
we need to make a copy of the mesh and put the aproximating
points onto the curve */
/* create a copy of the mesh */
copy = CopyMesh( mesh );
/* store off the original (potentially bad) normals */
MakeMeshNormals( *copy );
for( i = 0; i < numVerts; i++ )
VectorCopy( copy->verts[ i ].normal, mesh->verts[ i ].normal );
/* put the mesh on the curve */
PutMeshOnCurve( *copy );
/* find new normals (to take into account degenerate/flipped edges */
MakeMeshNormals( *copy );
for( i = 0; i < numVerts; i++ )
{
/* ydnar: only copy normals that are significantly different from the originals */
if( DotProduct( copy->verts[ i ].normal, mesh->verts[ i ].normal ) < 0.75f )
VectorCopy( copy->verts[ i ].normal, mesh->verts[ i ].normal );
}
/* free the old mesh */
FreeMesh( copy );
/* ydnar: gs mods: check for indexed shader */
if( si->indexed && p->im != NULL )
{
/* indexed */
indexed = qtrue;
/* get shader indexes for each point */
for( i = 0; i < numVerts; i++ )
{
shaderIndexes[ i ] = GetShaderIndexForPoint( p->im, p->eMins, p->eMaxs, mesh->verts[ i ].xyz );
offsets[ i ] = p->im->offsets[ shaderIndexes[ i ] ];
}
/* get matching shader and set alpha */
parent = si;
si = GetIndexedShader( parent, p->im, numVerts, shaderIndexes );
}
else
indexed = qfalse;
/* ydnar: gs mods */
ds = AllocDrawSurface( SURFACE_PATCH );
ds->entityNum = p->entityNum;
ds->castShadows = p->castShadows;
ds->recvShadows = p->recvShadows;
ds->shaderInfo = si;
ds->mapMesh = p;
ds->sampleSize = p->lightmapSampleSize;
ds->lightmapScale = p->lightmapScale; /* ydnar */
ds->patchWidth = mesh->width;
ds->patchHeight = mesh->height;
ds->numVerts = ds->patchWidth * ds->patchHeight;
ds->verts = safe_malloc( ds->numVerts * sizeof( *ds->verts ) );
memcpy( ds->verts, mesh->verts, ds->numVerts * sizeof( *ds->verts ) );
ds->fogNum = -1;
ds->planeNum = -1;
ds->longestCurve = p->longestCurve;
ds->maxIterations = p->maxIterations;
/* construct a plane from the first vert */
VectorCopy( mesh->verts[ 0 ].normal, plane );
plane[ 3 ] = DotProduct( mesh->verts[ 0 ].xyz, plane );
planar = qtrue;
/* spew forth errors */
if( VectorLength( plane ) < 0.001f )
Sys_Printf( "BOGUS " );
/* test each vert */
for( i = 1; i < ds->numVerts && planar; i++ )
{
/* normal test */
if( VectorCompare( plane, mesh->verts[ i ].normal ) == qfalse )
planar = qfalse;
/* point-plane test */
dist = DotProduct( mesh->verts[ i ].xyz, plane ) - plane[ 3 ];
if( fabs( dist ) > EQUAL_EPSILON )
planar = qfalse;
}
/* add a map plane */
if( planar )
{
/* make a map plane */
ds->planeNum = FindFloatPlane( plane, plane[ 3 ], 1, &mesh->verts[ 0 ].xyz );
VectorCopy( plane, ds->lightmapVecs[ 2 ] );
/* push this normal to all verts (ydnar 2003-02-14: bad idea, small patches get screwed up) */
for( i = 0; i < ds->numVerts; i++ )
VectorCopy( plane, ds->verts[ i ].normal );
}
/* walk the verts to do special stuff */
for( i = 0; i < ds->numVerts; i++ )
{
/* get the drawvert */
dv = &ds->verts[ i ];
/* ydnar: tek-fu celshading support for flat shaded shit */
if( flat )
{
dv->st[ 0 ] = si->stFlat[ 0 ];
dv->st[ 1 ] = si->stFlat[ 1 ];
}
/* ydnar: gs mods: added support for explicit shader texcoord generation */
else if( si->tcGen )
{
/* translate by origin and project the texture */
VectorAdd( dv->xyz, e->origin, vTranslated );
dv->st[ 0 ] = DotProduct( si->vecs[ 0 ], vTranslated );
dv->st[ 1 ] = DotProduct( si->vecs[ 1 ], vTranslated );
}
/* ydnar: set color */
for( k = 0; k < MAX_LIGHTMAPS; k++ )
{
dv->color[ k ][ 0 ] = 255;
dv->color[ k ][ 1 ] = 255;
dv->color[ k ][ 2 ] = 255;
/* ydnar: gs mods: handle indexed shader blending */
dv->color[ k ][ 3 ] = (indexed ? shaderIndexes[ i ] : 255);
}
/* ydnar: offset */
if( indexed )
dv->xyz[ 2 ] += offsets[ i ];
}
/* set cel shader */
ds->celShader = p->celShader;
/* return the drawsurface */
return ds;
}
/*
DrawSurfaceForFlare() - ydnar
creates a flare draw surface
*/
mapDrawSurface_t *DrawSurfaceForFlare( int entNum, vec3_t origin, vec3_t normal, vec3_t color, const char *flareShader, int lightStyle )
{
mapDrawSurface_t *ds;
/* emit flares? */
if( emitFlares == qfalse )
return NULL;
/* allocate drawsurface */
ds = AllocDrawSurface( SURFACE_FLARE );
ds->entityNum = entNum;
/* set it up */
if( flareShader != NULL && flareShader[ 0 ] != '\0' )
ds->shaderInfo = ShaderInfoForShader( flareShader );
else
ds->shaderInfo = ShaderInfoForShader( game->flareShader );
if( origin != NULL )
VectorCopy( origin, ds->lightmapOrigin );
if( normal != NULL )
VectorCopy( normal, ds->lightmapVecs[ 2 ] );
if( color != NULL )
VectorCopy( color, ds->lightmapVecs[ 0 ] );
/* store light style */
ds->lightStyle = lightStyle;
if( ds->lightStyle < 0 || ds->lightStyle >= LS_NONE )
ds->lightStyle = LS_NORMAL;
/* fixme: fog */
/* return to sender */
return ds;
}
/*
DrawSurfaceForShader() - ydnar
creates a bogus surface to forcing the game to load a shader
*/
mapDrawSurface_t *DrawSurfaceForShader( char *shader )
{
int i;
shaderInfo_t *si;
mapDrawSurface_t *ds;
/* get shader */
si = ShaderInfoForShader( shader );
/* find existing surface */
for( i = 0; i < numMapDrawSurfs; i++ )
{
/* get surface */
ds = &mapDrawSurfs[ i ];
/* check it */
if( ds->shaderInfo == si )
return ds;
}
/* create a new surface */
ds = AllocDrawSurface( SURFACE_SHADER );
ds->entityNum = 0;
ds->shaderInfo = ShaderInfoForShader( shader );
/* return to sender */
return ds;
}
/*
AddSurfaceFlare() - ydnar
creates flares (coronas) centered on surfaces
*/
static void AddSurfaceFlare( mapDrawSurface_t *ds, vec3_t entityOrigin )
{
vec3_t origin;
int i;
/* find centroid */
VectorClear( origin );
for ( i = 0; i < ds->numVerts; i++ )
VectorAdd( origin, ds->verts[ i ].xyz, origin );
VectorScale( origin, (1.0f / ds->numVerts), origin );
if( entityOrigin != NULL )
VectorAdd( origin, entityOrigin, origin );
/* push origin off surface a bit */
VectorMA( origin, 2.0f, ds->lightmapVecs[ 2 ], origin );
/* create the drawsurface */
DrawSurfaceForFlare( ds->entityNum, origin, ds->lightmapVecs[ 2 ], ds->shaderInfo->color, ds->shaderInfo->flareShader, ds->shaderInfo->lightStyle );
}
/*
SubdivideFace()
subdivides a face surface until it is smaller than the specified size (subdivisions)
*/
static void SubdivideFace_r( entity_t *e, brush_t *brush, side_t *side, winding_t *w, int fogNum, float subdivisions )
{
int i;
int axis;
vec3_t bounds[ 2 ];
const float epsilon = 0.1;
int subFloor, subCeil;
winding_t *frontWinding, *backWinding;
mapDrawSurface_t *ds;
/* dummy check */
if( w == NULL )
return;
if( w->numpoints < 3 )
Error( "SubdivideFace_r: Bad w->numpoints (%d < 3)", w->numpoints );
/* determine surface bounds */
ClearBounds( bounds[ 0 ], bounds[ 1 ] );
for( i = 0; i < w->numpoints; i++ )
AddPointToBounds( w->p[ i ], bounds[ 0 ], bounds[ 1 ] );
/* split the face */
for( axis = 0; axis < 3; axis++ )
{
vec3_t planePoint = { 0, 0, 0 };
vec3_t planeNormal = { 0, 0, 0 };
float d;
/* create an axial clipping plane */
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 )
{
/* clip the winding */
ClipWindingEpsilon( w, planeNormal, d, epsilon, &frontWinding, &backWinding ); /* not strict; we assume we always keep a winding */
/* the clip may not produce two polygons if it was epsilon close */
if( frontWinding == NULL )
w = backWinding;
else if( backWinding == NULL )
w = frontWinding;
else
{
SubdivideFace_r( e, brush, side, frontWinding, fogNum, subdivisions );
SubdivideFace_r( e, brush, side, backWinding, fogNum, subdivisions );
return;
}
}
}
/* create a face surface */
ds = DrawSurfaceForSide( e, brush, side, w );
/* set correct fog num */
ds->fogNum = fogNum;
}
/*
SubdivideFaceSurfaces()
chop up brush face surfaces that have subdivision attributes
ydnar: and subdivide surfaces that exceed specified texture coordinate range
*/
void SubdivideFaceSurfaces( entity_t *e, tree_t *tree )
{
int i, j, numBaseDrawSurfs, fogNum;
mapDrawSurface_t *ds;
brush_t *brush;
side_t *side;
shaderInfo_t *si;
winding_t *w;
float range, size, subdivisions, s2;
/* note it */
Sys_FPrintf( SYS_VRB, "--- SubdivideFaceSurfaces ---\n" );
/* walk the list of surfaces */
numBaseDrawSurfs = numMapDrawSurfs;
for( i = e->firstDrawSurf; i < numBaseDrawSurfs; i++ )
{
/* get surface */
ds = &mapDrawSurfs[ i ];
/* only subdivide brush sides */
if( ds->type != SURFACE_FACE || ds->mapBrush == NULL || ds->sideRef == NULL || ds->sideRef->side == NULL )
continue;
/* get bits */
brush = ds->mapBrush;
side = ds->sideRef->side;
/* check subdivision for shader */
si = side->shaderInfo;
if( si == NULL )
continue;
/* ydnar: don't subdivide sky surfaces */
if( si->compileFlags & C_SKY )
continue;
/* do texture coordinate range check */
ClassifySurfaces( 1, ds );
if( CalcSurfaceTextureRange( ds ) == qfalse )
{
/* calculate subdivisions texture range (this code is shit) */
range = (ds->texRange[ 0 ] > ds->texRange[ 1 ] ? ds->texRange[ 0 ] : ds->texRange[ 1 ]);
size = ds->maxs[ 0 ] - ds->mins[ 0 ];
for( j = 1; j < 3; j++ )
if( (ds->maxs[ j ] - ds->mins[ j ]) > size )
size = ds->maxs[ j ] - ds->mins[ j ];
subdivisions = (size / range) * texRange;
subdivisions = ceil( subdivisions / 2 ) * 2;
for( j = 1; j < 8; j++ )
{
s2 = ceil( (float) texRange / j );
if( fabs( subdivisions - s2 ) <= 4.0 )
{
subdivisions = s2;
break;
}
}
}
else
subdivisions = si->subdivisions;
/* get subdivisions from shader */
if( si->subdivisions > 0 && si->subdivisions < subdivisions )
subdivisions = si->subdivisions;
if( subdivisions < 1.0f )
continue;
/* preserve fog num */
fogNum = ds->fogNum;
/* make a winding and free the surface */
w = WindingFromDrawSurf( ds );
ClearSurface( ds );
/* subdivide it */
SubdivideFace_r( e, brush, side, w, fogNum, subdivisions );
}
}
/*
====================
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 ];
ClipWindingEpsilonStrict ( w, plane->normal, plane->dist,
ON_EPSILON, &front, &back ); /* strict, we handle the "winding disappeared" case */
if(!front && !back)
{
/* in doubt, register it in both nodes */
front = CopyWinding(w);
back = CopyWinding(w);
}
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;
}
static int g_numHiddenFaces, g_numCoinFaces;
/*
CullVectorCompare() - ydnar
compares two vectors with an epsilon
*/
#define CULL_EPSILON 0.1f
qboolean CullVectorCompare( const vec3_t v1, const vec3_t v2 )
{
int i;
for( i = 0; i < 3; i++ )
if( fabs( v1[ i ] - v2[ i ] ) > CULL_EPSILON )
return qfalse;
return qtrue;
}
/*
SideInBrush() - ydnar
determines if a brushside lies inside another brush
*/
qboolean SideInBrush( side_t *side, brush_t *b )
{
int i, s;
plane_t *plane;
/* ignore sides w/o windings or shaders */
if( side->winding == NULL || side->shaderInfo == NULL )
return qtrue;
/* ignore culled sides and translucent brushes */
if( side->culled == qtrue || (b->compileFlags & C_TRANSLUCENT) )
return qfalse;
/* side iterator */
for( i = 0; i < b->numsides; i++ )
{
/* fail if any sides are caulk */
if( b->sides[ i ].compileFlags & C_NODRAW )
return qfalse;
/* check if side's winding is on or behind the plane */
plane = &mapplanes[ b->sides[ i ].planenum ];
s = WindingOnPlaneSide( side->winding, plane->normal, plane->dist );
if( s == SIDE_FRONT || s == SIDE_CROSS )
return qfalse;
}
/* don't cull autosprite or polygonoffset surfaces */
if( side->shaderInfo )
{
if( side->shaderInfo->autosprite || side->shaderInfo->polygonOffset )
return qfalse;
}
/* inside */
side->culled = qtrue;
g_numHiddenFaces++;
return qtrue;
}
/*
CullSides() - ydnar
culls obscured or buried brushsides from the map
*/
void CullSides( entity_t *e )
{
int numPoints;
int i, j, k, l, first, second, dir;
winding_t *w1, *w2;
brush_t *b1, *b2;
side_t *side1, *side2;
/* note it */
Sys_FPrintf( SYS_VRB, "--- CullSides ---\n" );
g_numHiddenFaces = 0;
g_numCoinFaces = 0;
/* brush interator 1 */
for( b1 = e->brushes; b1; b1 = b1->next )
{
/* sides check */
if( b1->numsides < 1 )
continue;
/* brush iterator 2 */
for( b2 = b1->next; b2; b2 = b2->next )
{
/* sides check */
if( b2->numsides < 1 )
continue;
/* original check */
if( b1->original == b2->original && b1->original != NULL )
continue;
/* bbox check */
j = 0;
for( i = 0; i < 3; i++ )
if( b1->mins[ i ] > b2->maxs[ i ] || b1->maxs[ i ] < b2->mins[ i ] )
j++;
if( j )
continue;
/* cull inside sides */
for( i = 0; i < b1->numsides; i++ )
SideInBrush( &b1->sides[ i ], b2 );
for( i = 0; i < b2->numsides; i++ )
SideInBrush( &b2->sides[ i ], b1 );
/* side iterator 1 */
for( i = 0; i < b1->numsides; i++ )
{
/* winding check */
side1 = &b1->sides[ i ];
w1 = side1->winding;
if( w1 == NULL )
continue;
numPoints = w1->numpoints;
if( side1->shaderInfo == NULL )
continue;
/* side iterator 2 */
for( j = 0; j < b2->numsides; j++ )
{
/* winding check */
side2 = &b2->sides[ j ];
w2 = side2->winding;
if( w2 == NULL )
continue;
if( side2->shaderInfo == NULL )
continue;
if( w1->numpoints != w2->numpoints )
continue;
if( side1->culled == qtrue && side2->culled == qtrue )
continue;
/* compare planes */
if( (side1->planenum & ~0x00000001) != (side2->planenum & ~0x00000001) )
continue;
/* get autosprite and polygonoffset status */
if( side1->shaderInfo &&
(side1->shaderInfo->autosprite || side1->shaderInfo->polygonOffset) )
continue;
if( side2->shaderInfo &&
(side2->shaderInfo->autosprite || side2->shaderInfo->polygonOffset) )
continue;
/* find first common point */
first = -1;
for( k = 0; k < numPoints; k++ )
{
if( VectorCompare( w1->p[ 0 ], w2->p[ k ] ) )
{
first = k;
k = numPoints;
}
}
if( first == -1 )
continue;
/* find second common point (regardless of winding order) */
second = -1;
dir = 0;
if( (first + 1) < numPoints )
second = first + 1;
else
second = 0;
if( CullVectorCompare( w1->p[ 1 ], w2->p[ second ] ) )
dir = 1;
else
{
if( first > 0 )
second = first - 1;
else
second = numPoints - 1;
if( CullVectorCompare( w1->p[ 1 ], w2->p[ second ] ) )
dir = -1;
}
if( dir == 0 )
continue;
/* compare the rest of the points */
l = first;
for( k = 0; k < numPoints; k++ )
{
if( !CullVectorCompare( w1->p[ k ], w2->p[ l ] ) )
k = 100000;
l += dir;
if( l < 0 )
l = numPoints - 1;
else if( l >= numPoints )
l = 0;
}
if( k >= 100000 )
continue;
/* cull face 1 */
if( !side2->culled && !(side2->compileFlags & C_TRANSLUCENT) && !(side2->compileFlags & C_NODRAW) )
{
side1->culled = qtrue;
g_numCoinFaces++;
}
if( side1->planenum == side2->planenum && side1->culled == qtrue )
continue;
/* cull face 2 */
if( !side1->culled && !(side1->compileFlags & C_TRANSLUCENT) && !(side1->compileFlags & C_NODRAW) )
{
side2->culled = qtrue;
g_numCoinFaces++;
}
}
}
}
}
/* emit some stats */
Sys_FPrintf( SYS_VRB, "%9d hidden faces culled\n", g_numHiddenFaces );
Sys_FPrintf( SYS_VRB, "%9d coincident faces culled\n", g_numCoinFaces );
}
/*
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( entity_t *e, tree_t *tree )
{
brush_t *b;
int i;
winding_t *w;
side_t *side, *newSide;
shaderInfo_t *si;
/* ydnar: cull brush sides */
CullSides( e );
/* note it */
Sys_FPrintf( SYS_VRB, "--- ClipSidesIntoTree ---\n" );
/* walk the brush list */
for( b = e->brushes; b; b = b->next )
{
/* walk the brush sides */
for( i = 0; i < b->numsides; i++ )
{
/* get side */
side = &b->sides[ i ];
if( side->winding == NULL )
continue;
/* copy the winding */
w = CopyWinding( side->winding );
side->visibleHull = NULL;
ClipSideIntoTree_r( w, side, tree->headnode );
/* anything left? */
w = side->visibleHull;
if( w == NULL )
continue;
/* shader? */
si = side->shaderInfo;
if( si == NULL )
continue;
/* don't create faces for non-visible sides */
/* ydnar: except indexed shaders, like common/terrain and nodraw fog surfaces */
if( (si->compileFlags & C_NODRAW) && si->indexed == qfalse && !(si->compileFlags & C_FOG) )
continue;
/* always use the original winding for autosprites and noclip faces */
if( si->autosprite || si->noClip )
w = side->winding;
/* save this winding as a visible surface */
DrawSurfaceForSide( e, b, side, w );
/* make a back side for fog */
if( !(si->compileFlags & C_FOG) )
continue;
/* duplicate the up-facing side */
w = ReverseWinding( w );
newSide = safe_malloc( sizeof( *side ) );
*newSide = *side;
newSide->visibleHull = w;
newSide->planenum ^= 1;
/* save this winding as a visible surface */
DrawSurfaceForSide( e, b, newSide, w );
}
}
}
/*
this section deals with filtering drawsurfaces into the bsp tree,
adding references to each leaf a surface touches
*/
/*
AddReferenceToLeaf() - ydnar
adds a reference to surface ds in the bsp leaf node
*/
int AddReferenceToLeaf( mapDrawSurface_t *ds, node_t *node )
{
drawSurfRef_t *dsr;
/* dummy check */
if( node->planenum != PLANENUM_LEAF || node->opaque )
return 0;
/* try to find an existing reference */
for( dsr = node->drawSurfReferences; dsr; dsr = dsr->nextRef )
{
if( dsr->outputNum == numBSPDrawSurfaces )
return 0;
}
/* add a new reference */
dsr = safe_malloc( sizeof( *dsr ) );
dsr->outputNum = numBSPDrawSurfaces;
dsr->nextRef = node->drawSurfReferences;
node->drawSurfReferences = dsr;
/* ydnar: sky/skybox surfaces */
if( node->skybox )
ds->skybox = qtrue;
if( ds->shaderInfo->compileFlags & C_SKY )
node->sky = qtrue;
/* return */
return 1;
}
/*
AddReferenceToTree_r() - ydnar
adds a reference to the specified drawsurface to every leaf in the tree
*/
int AddReferenceToTree_r( mapDrawSurface_t *ds, node_t *node, qboolean skybox )
{
int i, refs = 0;
/* dummy check */
if( node == NULL )
return 0;
/* is this a decision node? */
if( node->planenum != PLANENUM_LEAF )
{
/* add to child nodes and return */
refs += AddReferenceToTree_r( ds, node->children[ 0 ], skybox );
refs += AddReferenceToTree_r( ds, node->children[ 1 ], skybox );
return refs;
}
/* ydnar */
if( skybox )
{
/* skybox surfaces only get added to sky leaves */
if( !node->sky )
return 0;
/* increase the leaf bounds */
for( i = 0; i < ds->numVerts; i++ )
AddPointToBounds( ds->verts[ i ].xyz, node->mins, node->maxs );
}
/* add a reference */
return AddReferenceToLeaf( ds, node );
}
/*
FilterPointIntoTree_r() - ydnar
filters a single point from a surface into the tree
*/
int FilterPointIntoTree_r( vec3_t point, mapDrawSurface_t *ds, node_t *node )
{
float d;
plane_t *plane;
int refs = 0;
/* is this a decision node? */
if( node->planenum != PLANENUM_LEAF )
{
/* classify the point in relation to the plane */
plane = &mapplanes[ node->planenum ];
d = DotProduct( point, plane->normal ) - plane->dist;
/* filter by this plane */
refs = 0;
if( d >= -ON_EPSILON )
refs += FilterPointIntoTree_r( point, ds, node->children[ 0 ] );
if( d <= ON_EPSILON )
refs += FilterPointIntoTree_r( point, ds, node->children[ 1 ] );
/* return */
return refs;
}
/* add a reference */
return AddReferenceToLeaf( ds, node );
}
/*
FilterPointConvexHullIntoTree_r() - ydnar
filters the convex hull of multiple points from a surface into the tree
*/
int FilterPointConvexHullIntoTree_r( vec3_t **points, int npoints, mapDrawSurface_t *ds, node_t *node )
{
float d, dmin, dmax;
plane_t *plane;
int refs = 0;
int i;
if(!points)
return 0;
/* is this a decision node? */
if( node->planenum != PLANENUM_LEAF )
{
/* classify the point in relation to the plane */
plane = &mapplanes[ node->planenum ];
dmin = dmax = DotProduct( *(points[0]), plane->normal ) - plane->dist;
for(i = 1; i < npoints; ++i)
{
d = DotProduct( *(points[i]), plane->normal ) - plane->dist;
if(d > dmax)
dmax = d;
if(d < dmin)
dmin = d;
}
/* filter by this plane */
refs = 0;
if( dmax >= -ON_EPSILON )
refs += FilterPointConvexHullIntoTree_r( points, npoints, ds, node->children[ 0 ] );
if( dmin <= ON_EPSILON )
refs += FilterPointConvexHullIntoTree_r( points, npoints, ds, node->children[ 1 ] );
/* return */
return refs;
}
/* add a reference */
return AddReferenceToLeaf( ds, node );
}
/*
FilterWindingIntoTree_r() - ydnar
filters a winding from a drawsurface into the tree
*/
int FilterWindingIntoTree_r( winding_t *w, mapDrawSurface_t *ds, node_t *node )
{
int i, refs = 0;
plane_t *p1, *p2;
vec4_t plane1, plane2;
winding_t *fat, *front, *back;
shaderInfo_t *si;
/* get shaderinfo */
si = ds->shaderInfo;
/* ydnar: is this the head node? */
if( node->parent == NULL && si != NULL &&
(si->mins[ 0 ] != 0.0f || si->maxs[ 0 ] != 0.0f ||
si->mins[ 1 ] != 0.0f || si->maxs[ 1 ] != 0.0f ||
si->mins[ 2 ] != 0.0f || si->maxs[ 2 ] != 0.0f) )
{
static qboolean warned = qfalse;
if(!warned)
{
Sys_Printf( "WARNING: this map uses the deformVertexes move hack\n" );
warned = qtrue;
}
/* 'fatten' the winding by the shader mins/maxs (parsed from vertexDeform move) */
/* note this winding is completely invalid (concave, nonplanar, etc) */
fat = AllocWinding( w->numpoints * 3 + 3 );
fat->numpoints = w->numpoints * 3 + 3;
for( i = 0; i < w->numpoints; i++ )
{
VectorCopy( w->p[ i ], fat->p[ i ] );
VectorAdd( w->p[ i ], si->mins, fat->p[ i + (w->numpoints+1) ] );
VectorAdd( w->p[ i ], si->maxs, fat->p[ i + (w->numpoints+1) * 2 ] );
}
VectorCopy( w->p[ 0 ], fat->p[ i ] );
VectorAdd( w->p[ 0 ], si->mins, fat->p[ i + w->numpoints ] );
VectorAdd( w->p[ 0 ], si->maxs, fat->p[ i + w->numpoints * 2 ] );
/*
* note: this winding is STILL not suitable for ClipWindingEpsilon, and
* also does not really fulfill the intention as it only contains
* origin, +mins, +maxs, but thanks to the "closing" points I just
* added to the three sub-windings, the fattening at least doesn't make
* it worse
*/
FreeWinding( w );
w = fat;
}
/* is this a decision node? */
if( node->planenum != PLANENUM_LEAF )
{
/* get node plane */
p1 = &mapplanes[ node->planenum ];
VectorCopy( p1->normal, plane1 );
plane1[ 3 ] = p1->dist;
/* check if surface is planar */
if( ds->planeNum >= 0 )
{
/* get surface plane */
p2 = &mapplanes[ ds->planeNum ];
VectorCopy( p2->normal, plane2 );
plane2[ 3 ] = p2->dist;
#if 0
/* div0: this is the plague (inaccurate) */
vec4_t reverse;
/* invert surface plane */
VectorSubtract( vec3_origin, plane2, reverse );
reverse[ 3 ] = -plane2[ 3 ];
/* compare planes */
if( DotProduct( plane1, plane2 ) > 0.999f && fabs( plane1[ 3 ] - plane2[ 3 ] ) < 0.001f )
return FilterWindingIntoTree_r( w, ds, node->children[ 0 ] );
if( DotProduct( plane1, reverse ) > 0.999f && fabs( plane1[ 3 ] - reverse[ 3 ] ) < 0.001f )
return FilterWindingIntoTree_r( w, ds, node->children[ 1 ] );
#else
/* div0: this is the cholera (doesn't hit enough) */
/* the drawsurf might have an associated plane, if so, force a filter here */
if( ds->planeNum == node->planenum )
return FilterWindingIntoTree_r( w, ds, node->children[ 0 ] );
if( ds->planeNum == (node->planenum ^ 1) )
return FilterWindingIntoTree_r( w, ds, node->children[ 1 ] );
#endif
}
/* clip the winding by this plane */
ClipWindingEpsilonStrict( w, plane1, plane1[ 3 ], ON_EPSILON, &front, &back ); /* strict; we handle the "winding disappeared" case */
/* filter by this plane */
refs = 0;
if( front == NULL && back == NULL )
{
/* same plane, this is an ugly hack */
/* but better too many than too few refs */
refs += FilterWindingIntoTree_r( CopyWinding(w), ds, node->children[ 0 ] );
refs += FilterWindingIntoTree_r( CopyWinding(w), ds, node->children[ 1 ] );
}
if( front != NULL )
refs += FilterWindingIntoTree_r( front, ds, node->children[ 0 ] );
if( back != NULL )
refs += FilterWindingIntoTree_r( back, ds, node->children[ 1 ] );
FreeWinding( w );
/* return */
return refs;
}
/* add a reference */
return AddReferenceToLeaf( ds, node );
}
/*
FilterFaceIntoTree()
filters a planar winding face drawsurface into the bsp tree
*/
int FilterFaceIntoTree( mapDrawSurface_t *ds, tree_t *tree )
{
winding_t *w;
int refs = 0;
/* make a winding and filter it into the tree */
w = WindingFromDrawSurf( ds );
refs = FilterWindingIntoTree_r( w, ds, tree->headnode );
/* return */
return refs;
}
/*
FilterPatchIntoTree()
subdivides a patch into an approximate curve and filters it into the tree
*/
#define FILTER_SUBDIVISION 8
static int FilterPatchIntoTree( mapDrawSurface_t *ds, tree_t *tree )
{
int x, y, refs = 0;
for(y = 0; y + 2 < ds->patchHeight; y += 2)
for(x = 0; x + 2 < ds->patchWidth; x += 2)
{
vec3_t *points[9];
points[0] = &ds->verts[(y+0) * ds->patchWidth + (x+0)].xyz;
points[1] = &ds->verts[(y+0) * ds->patchWidth + (x+1)].xyz;
points[2] = &ds->verts[(y+0) * ds->patchWidth + (x+2)].xyz;
points[3] = &ds->verts[(y+1) * ds->patchWidth + (x+0)].xyz;
points[4] = &ds->verts[(y+1) * ds->patchWidth + (x+1)].xyz;
points[5] = &ds->verts[(y+1) * ds->patchWidth + (x+2)].xyz;
points[6] = &ds->verts[(y+2) * ds->patchWidth + (x+0)].xyz;
points[7] = &ds->verts[(y+2) * ds->patchWidth + (x+1)].xyz;
points[8] = &ds->verts[(y+2) * ds->patchWidth + (x+2)].xyz;
refs += FilterPointConvexHullIntoTree_r(points, 9, ds, tree->headnode);
}
return refs;
}
/*
FilterTrianglesIntoTree()
filters a triangle surface (meta, model) into the bsp
*/
static int FilterTrianglesIntoTree( mapDrawSurface_t *ds, tree_t *tree )
{
int i, refs;
winding_t *w;
/* ydnar: gs mods: this was creating bogus triangles before */
refs = 0;
for( i = 0; i < ds->numIndexes; i += 3 )
{
/* error check */
if( ds->indexes[ i ] >= ds->numVerts ||
ds->indexes[ i + 1 ] >= ds->numVerts ||
ds->indexes[ i + 2 ] >= ds->numVerts )
Error( "Index %d greater than vertex count %d", ds->indexes[ i ], ds->numVerts );
/* make a triangle winding and filter it into the tree */
w = AllocWinding( 3 );
w->numpoints = 3;
VectorCopy( ds->verts[ ds->indexes[ i ] ].xyz, w->p[ 0 ] );
VectorCopy( ds->verts[ ds->indexes[ i + 1 ] ].xyz, w->p[ 1 ] );
VectorCopy( ds->verts[ ds->indexes[ i + 2 ] ].xyz, w->p[ 2 ] );
refs += FilterWindingIntoTree_r( w, ds, tree->headnode );
}
/* use point filtering as well */
for( i = 0; i < ds->numVerts; i++ )
refs += FilterPointIntoTree_r( ds->verts[ i ].xyz, ds, tree->headnode );
return refs;
}
/*
FilterFoliageIntoTree()
filters a foliage surface (wolf et/splash damage)
*/
static int FilterFoliageIntoTree( mapDrawSurface_t *ds, tree_t *tree )
{
int f, i, refs;
bspDrawVert_t *instance;
vec3_t xyz;
winding_t *w;
/* walk origin list */
refs = 0;
for( f = 0; f < ds->numFoliageInstances; f++ )
{
/* get instance */
instance = ds->verts + ds->patchHeight + f;
/* walk triangle list */
for( i = 0; i < ds->numIndexes; i += 3 )
{
/* error check */
if( ds->indexes[ i ] >= ds->numVerts ||
ds->indexes[ i + 1 ] >= ds->numVerts ||
ds->indexes[ i + 2 ] >= ds->numVerts )
Error( "Index %d greater than vertex count %d", ds->indexes[ i ], ds->numVerts );
/* make a triangle winding and filter it into the tree */
w = AllocWinding( 3 );
w->numpoints = 3;
VectorAdd( instance->xyz, ds->verts[ ds->indexes[ i ] ].xyz, w->p[ 0 ] );
VectorAdd( instance->xyz, ds->verts[ ds->indexes[ i + 1 ] ].xyz, w->p[ 1 ] );
VectorAdd( instance->xyz, ds->verts[ ds->indexes[ i + 2 ] ].xyz, w->p[ 2 ] );
refs += FilterWindingIntoTree_r( w, ds, tree->headnode );
}
/* use point filtering as well */
for( i = 0; i < (ds->numVerts - ds->numFoliageInstances); i++ )
{
VectorAdd( instance->xyz, ds->verts[ i ].xyz, xyz );
refs += FilterPointIntoTree_r( xyz, ds, tree->headnode );
}
}
return refs;
}
/*
FilterFlareIntoTree()
simple point filtering for flare surfaces
*/
static int FilterFlareSurfIntoTree( mapDrawSurface_t *ds, tree_t *tree )
{
return FilterPointIntoTree_r( ds->lightmapOrigin, ds, tree->headnode );
}
/*
EmitDrawVerts() - ydnar
emits bsp drawverts from a map drawsurface
*/
void EmitDrawVerts( mapDrawSurface_t *ds, bspDrawSurface_t *out )
{
int i, k;
bspDrawVert_t *dv;
shaderInfo_t *si;
float offset;
/* get stuff */
si = ds->shaderInfo;
offset = si->offset;
/* copy the verts */
out->firstVert = numBSPDrawVerts;
out->numVerts = ds->numVerts;
for( i = 0; i < ds->numVerts; i++ )
{
/* allocate a new vert */
IncDrawVerts();
dv = &bspDrawVerts[ numBSPDrawVerts - 1 ];
/* copy it */
memcpy( dv, &ds->verts[ i ], sizeof( *dv ) );
/* offset? */
if( offset != 0.0f )
VectorMA( dv->xyz, offset, dv->normal, dv->xyz );
/* expand model bounds
necessary because of misc_model surfaces on entities
note: does not happen on worldspawn as its bounds is only used for determining lightgrid bounds */
if( numBSPModels > 0 )
AddPointToBounds( dv->xyz, bspModels[ numBSPModels ].mins, bspModels[ numBSPModels ].maxs );
/* debug color? */
if( debugSurfaces )
{
for( k = 0; k < MAX_LIGHTMAPS; k++ )
VectorCopy( debugColors[ (ds - mapDrawSurfs) % 12 ], dv->color[ k ] );
}
}
}
/*
FindDrawIndexes() - ydnar
this attempts to find a run of indexes in the bsp that match the given indexes
this tends to reduce the size of the bsp index pool by 1/3 or more
returns numIndexes + 1 if the search failed
*/
int FindDrawIndexes( int numIndexes, int *indexes )
{
int i, j, numTestIndexes;
/* dummy check */
if( numIndexes < 3 || numBSPDrawIndexes < numIndexes || indexes == NULL )
return numBSPDrawIndexes;
/* set limit */
numTestIndexes = 1 + numBSPDrawIndexes - numIndexes;
/* handle 3 indexes as a special case for performance */
if( numIndexes == 3 )
{
/* run through all indexes */
for( i = 0; i < numTestIndexes; i++ )
{
/* test 3 indexes */
if( indexes[ 0 ] == bspDrawIndexes[ i ] &&
indexes[ 1 ] == bspDrawIndexes[ i + 1 ] &&
indexes[ 2 ] == bspDrawIndexes[ i + 2 ] )
{
numRedundantIndexes += numIndexes;
return i;
}
}
/* failed */
return numBSPDrawIndexes;
}
/* handle 4 or more indexes */
for( i = 0; i < numTestIndexes; i++ )
{
/* test first 4 indexes */
if( indexes[ 0 ] == bspDrawIndexes[ i ] &&
indexes[ 1 ] == bspDrawIndexes[ i + 1 ] &&
indexes[ 2 ] == bspDrawIndexes[ i + 2 ] &&
indexes[ 3 ] == bspDrawIndexes[ i + 3 ] )
{
/* handle 4 indexes */
if( numIndexes == 4 )
return i;
/* test the remainder */
for( j = 4; j < numIndexes; j++ )
{
if( indexes[ j ] != bspDrawIndexes[ i + j ] )
break;
else if( j == (numIndexes - 1) )
{
numRedundantIndexes += numIndexes;
return i;
}
}
}
}
/* failed */
return numBSPDrawIndexes;
}
/*
EmitDrawIndexes() - ydnar
attempts to find an existing run of drawindexes before adding new ones
*/
void EmitDrawIndexes( mapDrawSurface_t *ds, bspDrawSurface_t *out )
{
int i;
/* attempt to use redundant indexing */
out->firstIndex = FindDrawIndexes( ds->numIndexes, ds->indexes );
out->numIndexes = ds->numIndexes;
if( out->firstIndex == numBSPDrawIndexes )
{
/* copy new unique indexes */
for( i = 0; i < ds->numIndexes; i++ )
{
AUTOEXPAND_BY_REALLOC_BSP(DrawIndexes, 1024);
bspDrawIndexes[ numBSPDrawIndexes ] = ds->indexes[ i ];
/* validate the index */
if( ds->type != SURFACE_PATCH )
{
if( bspDrawIndexes[ numBSPDrawIndexes ] < 0 || bspDrawIndexes[ numBSPDrawIndexes ] >= ds->numVerts )
{
Sys_Printf( "WARNING: %d %s has invalid index %d (%d)\n",
numBSPDrawSurfaces,
ds->shaderInfo->shader,
bspDrawIndexes[ numBSPDrawIndexes ],
i );
bspDrawIndexes[ numBSPDrawIndexes ] = 0;
}
}
/* increment index count */
numBSPDrawIndexes++;
}
}
}
/*
EmitFlareSurface()
emits a bsp flare drawsurface
*/
void EmitFlareSurface( mapDrawSurface_t *ds )
{
int i;
bspDrawSurface_t *out;
/* ydnar: nuking useless flare drawsurfaces */
if( emitFlares == qfalse && ds->type != SURFACE_SHADER )
return;
/* limit check */
if( numBSPDrawSurfaces == MAX_MAP_DRAW_SURFS )
Error( "MAX_MAP_DRAW_SURFS" );
/* allocate a new surface */
if( numBSPDrawSurfaces == MAX_MAP_DRAW_SURFS )
Error( "MAX_MAP_DRAW_SURFS" );
out = &bspDrawSurfaces[ numBSPDrawSurfaces ];
ds->outputNum = numBSPDrawSurfaces;
numBSPDrawSurfaces++;
memset( out, 0, sizeof( *out ) );
/* set it up */
out->surfaceType = MST_FLARE;
out->shaderNum = EmitShader( ds->shaderInfo->shader, &ds->shaderInfo->contentFlags, &ds->shaderInfo->surfaceFlags );
out->fogNum = ds->fogNum;
/* RBSP */
for( i = 0; i < MAX_LIGHTMAPS; i++ )
{
out->lightmapNum[ i ] = -3;
out->lightmapStyles[ i ] = LS_NONE;
out->vertexStyles[ i ] = LS_NONE;
}
out->lightmapStyles[ 0 ] = ds->lightStyle;
out->vertexStyles[ 0 ] = ds->lightStyle;
VectorCopy( ds->lightmapOrigin, out->lightmapOrigin ); /* origin */
VectorCopy( ds->lightmapVecs[ 0 ], out->lightmapVecs[ 0 ] ); /* color */
VectorCopy( ds->lightmapVecs[ 1 ], out->lightmapVecs[ 1 ] );
VectorCopy( ds->lightmapVecs[ 2 ], out->lightmapVecs[ 2 ] ); /* normal */
/* add to count */
numSurfacesByType[ ds->type ]++;
}
/*
EmitPatchSurface()
emits a bsp patch drawsurface
*/
void EmitPatchSurface( entity_t *e, mapDrawSurface_t *ds )
{
int i, j;
bspDrawSurface_t *out;
int surfaceFlags, contentFlags;
int forcePatchMeta;
/* vortex: _patchMeta support */
forcePatchMeta = IntForKey(e, "_patchMeta" );
if (!forcePatchMeta)
forcePatchMeta = IntForKey(e, "patchMeta" );
/* invert the surface if necessary */
if( ds->backSide || ds->shaderInfo->invert )
{
bspDrawVert_t *dv1, *dv2, temp;
/* walk the verts, flip the normal */
for( i = 0; i < ds->numVerts; i++ )
VectorScale( ds->verts[ i ].normal, -1.0f, ds->verts[ i ].normal );
/* walk the verts again, but this time reverse their order */
for( j = 0; j < ds->patchHeight; j++ )
{
for( i = 0; i < (ds->patchWidth / 2); i++ )
{
dv1 = &ds->verts[ j * ds->patchWidth + i ];
dv2 = &ds->verts[ j * ds->patchWidth + (ds->patchWidth - i - 1) ];
memcpy( &temp, dv1, sizeof( bspDrawVert_t ) );
memcpy( dv1, dv2, sizeof( bspDrawVert_t ) );
memcpy( dv2, &temp, sizeof( bspDrawVert_t ) );
}
}
/* invert facing */
VectorScale( ds->lightmapVecs[ 2 ], -1.0f, ds->lightmapVecs[ 2 ] );
}
/* allocate a new surface */
if( numBSPDrawSurfaces == MAX_MAP_DRAW_SURFS )
Error( "MAX_MAP_DRAW_SURFS" );
out = &bspDrawSurfaces[ numBSPDrawSurfaces ];
ds->outputNum = numBSPDrawSurfaces;
numBSPDrawSurfaces++;
memset( out, 0, sizeof( *out ) );
/* set it up */
out->surfaceType = MST_PATCH;
if( debugSurfaces )
out->shaderNum = EmitShader( "debugsurfaces", NULL, NULL );
else if( patchMeta || forcePatchMeta )
{
/* patch meta requires that we have nodraw patches for collision */
surfaceFlags = ds->shaderInfo->surfaceFlags;
contentFlags = ds->shaderInfo->contentFlags;
ApplySurfaceParm( "nodraw", &contentFlags, &surfaceFlags, NULL );
ApplySurfaceParm( "pointlight", &contentFlags, &surfaceFlags, NULL );
/* we don't want this patch getting lightmapped */
VectorClear( ds->lightmapVecs[ 2 ] );
VectorClear( ds->lightmapAxis );
ds->sampleSize = 0;
/* emit the new fake shader */
out->shaderNum = EmitShader( ds->shaderInfo->shader, &contentFlags, &surfaceFlags );
}
else
out->shaderNum = EmitShader( ds->shaderInfo->shader, &ds->shaderInfo->contentFlags, &ds->shaderInfo->surfaceFlags );
out->patchWidth = ds->patchWidth;
out->patchHeight = ds->patchHeight;
out->fogNum = ds->fogNum;
/* RBSP */
for( i = 0; i < MAX_LIGHTMAPS; i++ )
{
out->lightmapNum[ i ] = -3;
out->lightmapStyles[ i ] = LS_NONE;
out->vertexStyles[ i ] = LS_NONE;
}
out->lightmapStyles[ 0 ] = LS_NORMAL;
out->vertexStyles[ 0 ] = LS_NORMAL;
/* ydnar: gs mods: previously, the lod bounds were stored in lightmapVecs[ 0 ] and [ 1 ], moved to bounds[ 0 ] and [ 1 ] */
VectorCopy( ds->lightmapOrigin, out->lightmapOrigin );
VectorCopy( ds->bounds[ 0 ], out->lightmapVecs[ 0 ] );
VectorCopy( ds->bounds[ 1 ], out->lightmapVecs[ 1 ] );
VectorCopy( ds->lightmapVecs[ 2 ], out->lightmapVecs[ 2 ] );
/* ydnar: gs mods: clear out the plane normal */
if( ds->planar == qfalse )
VectorClear( out->lightmapVecs[ 2 ] );
/* emit the verts and indexes */
EmitDrawVerts( ds, out );
EmitDrawIndexes( ds, out );
/* add to count */
numSurfacesByType[ ds->type ]++;
}
/*
OptimizeTriangleSurface() - ydnar
optimizes the vertex/index data in a triangle surface
*/
#define VERTEX_CACHE_SIZE 16
static void OptimizeTriangleSurface( mapDrawSurface_t *ds )
{
int i, j, k, temp, first, best, bestScore, score;
int vertexCache[ VERTEX_CACHE_SIZE + 1 ]; /* one more for optimizing insert */
int *indexes;
/* certain surfaces don't get optimized */
if( ds->numIndexes <= VERTEX_CACHE_SIZE ||
ds->shaderInfo->autosprite )
return;
/* create index scratch pad */
indexes = safe_malloc( ds->numIndexes * sizeof( *indexes ) );
memcpy( indexes, ds->indexes, ds->numIndexes * sizeof( *indexes ) );
/* setup */
for( i = 0; i <= VERTEX_CACHE_SIZE && i < ds->numIndexes; i++ )
vertexCache[ i ] = indexes[ i ];
/* add triangles in a vertex cache-aware order */
for( i = 0; i < ds->numIndexes; i += 3 )
{
/* find best triangle given the current vertex cache */
first = -1;
best = -1;
bestScore = -1;
for( j = 0; j < ds->numIndexes; j += 3 )
{
/* valid triangle? */
if( indexes[ j ] != -1 )
{
/* set first if necessary */
if( first < 0 )
first = j;
/* score the triangle */
score = 0;
for( k = 0; k < VERTEX_CACHE_SIZE; k++ )
{
if( indexes[ j ] == vertexCache[ k ] || indexes[ j + 1 ] == vertexCache[ k ] || indexes[ j + 2 ] == vertexCache[ k ] )
score++;
}
/* better triangle? */
if( score > bestScore )
{
bestScore = score;
best = j;
}
/* a perfect score of 3 means this triangle's verts are already present in the vertex cache */
if( score == 3 )
break;
}
}
/* check if no decent triangle was found, and use first available */
if( best < 0 )
best = first;
/* valid triangle? */
if( best >= 0 )
{
/* add triangle to vertex cache */
for( j = 0; j < 3; j++ )
{
for( k = 0; k < VERTEX_CACHE_SIZE; k++ )
{
if( indexes[ best + j ] == vertexCache[ k ] )
break;
}
if( k >= VERTEX_CACHE_SIZE )
{
/* pop off top of vertex cache */
for( k = VERTEX_CACHE_SIZE; k > 0; k-- )
vertexCache[ k ] = vertexCache[ k - 1 ];
/* add vertex */
vertexCache[ 0 ] = indexes[ best + j ];
}
}
/* add triangle to surface */
ds->indexes[ i ] = indexes[ best ];
ds->indexes[ i + 1 ] = indexes[ best + 1 ];
ds->indexes[ i + 2 ] = indexes[ best + 2 ];
/* clear from input pool */
indexes[ best ] = -1;
indexes[ best + 1 ] = -1;
indexes[ best + 2 ] = -1;
/* sort triangle windings (312 -> 123) */
while( ds->indexes[ i ] > ds->indexes[ i + 1 ] || ds->indexes[ i ] > ds->indexes[ i + 2 ] )
{
temp = ds->indexes[ i ];
ds->indexes[ i ] = ds->indexes[ i + 1 ];
ds->indexes[ i + 1 ] = ds->indexes[ i + 2 ];
ds->indexes[ i + 2 ] = temp;
}
}
}
/* clean up */
free( indexes );
}
/*
EmitTriangleSurface()
creates a bsp drawsurface from arbitrary triangle surfaces
*/
void EmitTriangleSurface( mapDrawSurface_t *ds )
{
int i, temp;
bspDrawSurface_t *out;
/* invert the surface if necessary */
if( ds->backSide || ds->shaderInfo->invert )
{
/* walk the indexes, reverse the triangle order */
for( i = 0; i < ds->numIndexes; i += 3 )
{
temp = ds->indexes[ i ];
ds->indexes[ i ] = ds->indexes[ i + 1 ];
ds->indexes[ i + 1 ] = temp;
}
/* walk the verts, flip the normal */
for( i = 0; i < ds->numVerts; i++ )
VectorScale( ds->verts[ i ].normal, -1.0f, ds->verts[ i ].normal );
/* invert facing */
VectorScale( ds->lightmapVecs[ 2 ], -1.0f, ds->lightmapVecs[ 2 ] );
}
/* allocate a new surface */
if( numBSPDrawSurfaces == MAX_MAP_DRAW_SURFS )
Error( "MAX_MAP_DRAW_SURFS" );
out = &bspDrawSurfaces[ numBSPDrawSurfaces ];
ds->outputNum = numBSPDrawSurfaces;
numBSPDrawSurfaces++;
memset( out, 0, sizeof( *out ) );
/* ydnar/sd: handle wolf et foliage surfaces */
if( ds->type == SURFACE_FOLIAGE )
out->surfaceType = MST_FOLIAGE;
/* ydnar: gs mods: handle lightmapped terrain (force to planar type) */
//% else if( VectorLength( ds->lightmapAxis ) <= 0.0f || ds->type == SURFACE_TRIANGLES || ds->type == SURFACE_FOGHULL || debugSurfaces )
else if( (VectorLength( ds->lightmapAxis ) <= 0.0f && ds->planar == qfalse) ||
ds->type == SURFACE_TRIANGLES ||
ds->type == SURFACE_FOGHULL ||
ds->numVerts > maxLMSurfaceVerts ||
debugSurfaces )
out->surfaceType = MST_TRIANGLE_SOUP;
/* set to a planar face */
else
out->surfaceType = MST_PLANAR;
/* set it up */
if( debugSurfaces )
out->shaderNum = EmitShader( "debugsurfaces", NULL, NULL );
else
out->shaderNum = EmitShader( ds->shaderInfo->shader, &ds->shaderInfo->contentFlags, &ds->shaderInfo->surfaceFlags );
out->patchWidth = ds->patchWidth;
out->patchHeight = ds->patchHeight;
out->fogNum = ds->fogNum;
/* debug inset (push each triangle vertex towards the center of each triangle it is on */
if( debugInset )
{
bspDrawVert_t *a, *b, *c;
vec3_t cent, dir;
/* walk triangle list */
for( i = 0; i < ds->numIndexes; i += 3 )
{
/* get verts */
a = &ds->verts[ ds->indexes[ i ] ];
b = &ds->verts[ ds->indexes[ i + 1 ] ];
c = &ds->verts[ ds->indexes[ i + 2 ] ];
/* calculate centroid */
VectorCopy( a->xyz, cent );
VectorAdd( cent, b->xyz, cent );
VectorAdd( cent, c->xyz, cent );
VectorScale( cent, 1.0f / 3.0f, cent );
/* offset each vertex */
VectorSubtract( cent, a->xyz, dir );
VectorNormalize( dir, dir );
VectorAdd( a->xyz, dir, a->xyz );
VectorSubtract( cent, b->xyz, dir );
VectorNormalize( dir, dir );
VectorAdd( b->xyz, dir, b->xyz );
VectorSubtract( cent, c->xyz, dir );
VectorNormalize( dir, dir );
VectorAdd( c->xyz, dir, c->xyz );
}
}
/* RBSP */
for( i = 0; i < MAX_LIGHTMAPS; i++ )
{
out->lightmapNum[ i ] = -3;
out->lightmapStyles[ i ] = LS_NONE;
out->vertexStyles[ i ] = LS_NONE;
}
out->lightmapStyles[ 0 ] = LS_NORMAL;
out->vertexStyles[ 0 ] = LS_NORMAL;
/* lightmap vectors (lod bounds for patches */
VectorCopy( ds->lightmapOrigin, out->lightmapOrigin );
VectorCopy( ds->lightmapVecs[ 0 ], out->lightmapVecs[ 0 ] );
VectorCopy( ds->lightmapVecs[ 1 ], out->lightmapVecs[ 1 ] );
VectorCopy( ds->lightmapVecs[ 2 ], out->lightmapVecs[ 2 ] );
/* ydnar: gs mods: clear out the plane normal */
if( ds->planar == qfalse )
VectorClear( out->lightmapVecs[ 2 ] );
/* optimize the surface's triangles */
OptimizeTriangleSurface( ds );
/* emit the verts and indexes */
EmitDrawVerts( ds, out );
EmitDrawIndexes( ds, out );
/* add to count */
numSurfacesByType[ ds->type ]++;
}
/*
EmitFaceSurface()
emits a bsp planar winding (brush face) drawsurface
*/
static void EmitFaceSurface(mapDrawSurface_t *ds )
{
/* strip/fan finding was moved elsewhere */
if(maxAreaFaceSurface)
MaxAreaFaceSurface( ds );
else
StripFaceSurface( ds );
EmitTriangleSurface(ds);
}
/*
MakeDebugPortalSurfs_r() - ydnar
generates drawsurfaces for passable portals in the bsp
*/
static void MakeDebugPortalSurfs_r( node_t *node, shaderInfo_t *si )
{
int i, k, c, s;
portal_t *p;
winding_t *w;
mapDrawSurface_t *ds;
bspDrawVert_t *dv;
/* recurse if decision node */
if( node->planenum != PLANENUM_LEAF)
{
MakeDebugPortalSurfs_r( node->children[ 0 ], si );
MakeDebugPortalSurfs_r( node->children[ 1 ], si );
return;
}
/* don't bother with opaque leaves */
if( node->opaque )
return;
/* walk the list of portals */
for( c = 0, p = node->portals; p != NULL; c++, p = p->next[ s ] )
{
/* get winding and side even/odd */
w = p->winding;
s = (p->nodes[ 1 ] == node);
/* is this a valid portal for this leaf? */
if( w && p->nodes[ 0 ] == node )
{
/* is this portal passable? */
if( PortalPassable( p ) == qfalse )
continue;
/* check max points */
if( w->numpoints > 64 )
Error( "MakePortalSurfs_r: w->numpoints = %d", w->numpoints );
/* allocate a drawsurface */
ds = AllocDrawSurface( SURFACE_FACE );
ds->shaderInfo = si;
ds->planar = qtrue;
ds->sideRef = AllocSideRef( p->side, NULL );
ds->planeNum = FindFloatPlane( p->plane.normal, p->plane.dist, 0, NULL );
VectorCopy( p->plane.normal, ds->lightmapVecs[ 2 ] );
ds->fogNum = -1;
ds->numVerts = w->numpoints;
ds->verts = safe_malloc( ds->numVerts * sizeof( *ds->verts ) );
memset( ds->verts, 0, ds->numVerts * sizeof( *ds->verts ) );
/* walk the winding */
for( i = 0; i < ds->numVerts; i++ )
{
/* get vert */
dv = ds->verts + i;
/* set it */
VectorCopy( w->p[ i ], dv->xyz );
VectorCopy( p->plane.normal, dv->normal );
dv->st[ 0 ] = 0;
dv->st[ 1 ] = 0;
for( k = 0; k < MAX_LIGHTMAPS; k++ )
{
VectorCopy( debugColors[ c % 12 ], dv->color[ k ] );
dv->color[ k ][ 3 ] = 32;
}
}
}
}
}
/*
MakeDebugPortalSurfs() - ydnar
generates drawsurfaces for passable portals in the bsp
*/
void MakeDebugPortalSurfs( tree_t *tree )
{
shaderInfo_t *si;
/* note it */
Sys_FPrintf( SYS_VRB, "--- MakeDebugPortalSurfs ---\n" );
/* get portal debug shader */
si = ShaderInfoForShader( "debugportals" );
/* walk the tree */
MakeDebugPortalSurfs_r( tree->headnode, si );
}
/*
MakeFogHullSurfs()
generates drawsurfaces for a foghull (this MUST use a sky shader)
*/
void MakeFogHullSurfs( entity_t *e, tree_t *tree, char *shader )
{
shaderInfo_t *si;
mapDrawSurface_t *ds;
vec3_t fogMins, fogMaxs;
int i, indexes[] =
{
0, 1, 2, 0, 2, 3,
4, 7, 5, 5, 7, 6,
1, 5, 6, 1, 6, 2,
0, 4, 5, 0, 5, 1,
2, 6, 7, 2, 7, 3,
3, 7, 4, 3, 4, 0
};
/* dummy check */
if( shader == NULL || shader[ 0 ] == '\0' )
return;
/* note it */
Sys_FPrintf( SYS_VRB, "--- MakeFogHullSurfs ---\n" );
/* get hull bounds */
VectorCopy( mapMins, fogMins );
VectorCopy( mapMaxs, fogMaxs );
for( i = 0; i < 3; i++ )
{
fogMins[ i ] -= 128;
fogMaxs[ i ] += 128;
}
/* get foghull shader */
si = ShaderInfoForShader( shader );
/* allocate a drawsurface */
ds = AllocDrawSurface( SURFACE_FOGHULL );
ds->shaderInfo = si;
ds->fogNum = -1;
ds->numVerts = 8;
ds->verts = safe_malloc( ds->numVerts * sizeof( *ds->verts ) );
memset( ds->verts, 0, ds->numVerts * sizeof( *ds->verts ) );
ds->numIndexes = 36;
ds->indexes = safe_malloc( ds->numIndexes * sizeof( *ds->indexes ) );
memset( ds->indexes, 0, ds->numIndexes * sizeof( *ds->indexes ) );
/* set verts */
VectorSet( ds->verts[ 0 ].xyz, fogMins[ 0 ], fogMins[ 1 ], fogMins[ 2 ] );
VectorSet( ds->verts[ 1 ].xyz, fogMins[ 0 ], fogMaxs[ 1 ], fogMins[ 2 ] );
VectorSet( ds->verts[ 2 ].xyz, fogMaxs[ 0 ], fogMaxs[ 1 ], fogMins[ 2 ] );
VectorSet( ds->verts[ 3 ].xyz, fogMaxs[ 0 ], fogMins[ 1 ], fogMins[ 2 ] );
VectorSet( ds->verts[ 4 ].xyz, fogMins[ 0 ], fogMins[ 1 ], fogMaxs[ 2 ] );
VectorSet( ds->verts[ 5 ].xyz, fogMins[ 0 ], fogMaxs[ 1 ], fogMaxs[ 2 ] );
VectorSet( ds->verts[ 6 ].xyz, fogMaxs[ 0 ], fogMaxs[ 1 ], fogMaxs[ 2 ] );
VectorSet( ds->verts[ 7 ].xyz, fogMaxs[ 0 ], fogMins[ 1 ], fogMaxs[ 2 ] );
/* set indexes */
memcpy( ds->indexes, indexes, ds->numIndexes * sizeof( *ds->indexes ) );
}
/*
BiasSurfaceTextures()
biases a surface's texcoords as close to 0 as possible
*/
void BiasSurfaceTextures( mapDrawSurface_t *ds )
{
int i;
/* calculate the surface texture bias */
CalcSurfaceTextureRange( ds );
/* don't bias globaltextured shaders */
if( ds->shaderInfo->globalTexture )
return;
/* bias the texture coordinates */
for( i = 0; i < ds->numVerts; i++ )
{
ds->verts[ i ].st[ 0 ] += ds->bias[ 0 ];
ds->verts[ i ].st[ 1 ] += ds->bias[ 1 ];
}
}
/*
AddSurfaceModelsToTriangle_r()
adds models to a specified triangle, returns the number of models added
*/
int AddSurfaceModelsToTriangle_r( mapDrawSurface_t *ds, surfaceModel_t *model, bspDrawVert_t **tri )
{
bspDrawVert_t mid, *tri2[ 3 ];
int max, n, localNumSurfaceModels;
/* init */
localNumSurfaceModels = 0;
/* subdivide calc */
{
int i;
float *a, *b, dx, dy, dz, dist, maxDist;
/* find the longest edge and split it */
max = -1;
maxDist = 0.0f;
for( i = 0; i < 3; i++ )
{
/* get verts */
a = tri[ i ]->xyz;
b = tri[ (i + 1) % 3 ]->xyz;
/* get dists */
dx = a[ 0 ] - b[ 0 ];
dy = a[ 1 ] - b[ 1 ];
dz = a[ 2 ] - b[ 2 ];
dist = (dx * dx) + (dy * dy) + (dz * dz);
/* longer? */
if( dist > maxDist )
{
maxDist = dist;
max = i;
}
}
/* is the triangle small enough? */
if( max < 0 || maxDist <= (model->density * model->density) )
{
float odds, r, angle;
vec3_t origin, normal, scale, axis[ 3 ], angles;
m4x4_t transform, temp;
/* roll the dice (model's odds scaled by vertex alpha) */
odds = model->odds * (tri[ 0 ]->color[ 0 ][ 3 ] + tri[ 0 ]->color[ 0 ][ 3 ] + tri[ 0 ]->color[ 0 ][ 3 ]) / 765.0f;
r = Random();
if( r > odds )
return 0;
/* calculate scale */
r = model->minScale + Random() * (model->maxScale - model->minScale);
VectorSet( scale, r, r, r );
/* calculate angle */
angle = model->minAngle + Random() * (model->maxAngle - model->minAngle);
/* calculate average origin */
VectorCopy( tri[ 0 ]->xyz, origin );
VectorAdd( origin, tri[ 1 ]->xyz, origin );
VectorAdd( origin, tri[ 2 ]->xyz, origin );
VectorScale( origin, (1.0f / 3.0f), origin );
/* clear transform matrix */
m4x4_identity( transform );
/* handle oriented models */
if( model->oriented )
{
/* set angles */
VectorSet( angles, 0.0f, 0.0f, angle );
/* calculate average normal */
VectorCopy( tri[ 0 ]->normal, normal );
VectorAdd( normal, tri[ 1 ]->normal, normal );
VectorAdd( normal, tri[ 2 ]->normal, normal );
if( VectorNormalize( normal, axis[ 2 ] ) == 0.0f )
VectorCopy( tri[ 0 ]->normal, axis[ 2 ] );
/* make perpendicular vectors */
MakeNormalVectors( axis[ 2 ], axis[ 1 ], axis[ 0 ] );
/* copy to matrix */
m4x4_identity( temp );
temp[ 0 ] = axis[ 0 ][ 0 ]; temp[ 1 ] = axis[ 0 ][ 1 ]; temp[ 2 ] = axis[ 0 ][ 2 ];
temp[ 4 ] = axis[ 1 ][ 0 ]; temp[ 5 ] = axis[ 1 ][ 1 ]; temp[ 6 ] = axis[ 1 ][ 2 ];
temp[ 8 ] = axis[ 2 ][ 0 ]; temp[ 9 ] = axis[ 2 ][ 1 ]; temp[ 10 ] = axis[ 2 ][ 2 ];
/* scale */
m4x4_scale_by_vec3( temp, scale );
/* rotate around z axis */
m4x4_rotate_by_vec3( temp, angles, eXYZ );
/* translate */
m4x4_translate_by_vec3( transform, origin );
/* tranform into axis space */
m4x4_multiply_by_m4x4( transform, temp );
}
/* handle z-up models */
else
{
/* set angles */
VectorSet( angles, 0.0f, 0.0f, angle );
/* set matrix */
m4x4_pivoted_transform_by_vec3( transform, origin, angles, eXYZ, scale, vec3_origin );
}
/* insert the model */
InsertModel( (char *) model->model, 0, 0, transform, NULL, ds->celShader, ds->entityNum, ds->castShadows, ds->recvShadows, 0, ds->lightmapScale, 0, 0 );
/* return to sender */
return 1;
}
}
/* split the longest edge and map it */
LerpDrawVert( tri[ max ], tri[ (max + 1) % 3 ], &mid );
/* recurse to first triangle */
VectorCopy( tri, tri2 );
tri2[ max ] = &mid;
n = AddSurfaceModelsToTriangle_r( ds, model, tri2 );
if( n < 0 )
return n;
localNumSurfaceModels += n;
/* recurse to second triangle */
VectorCopy( tri, tri2 );
tri2[ (max + 1) % 3 ] = &mid;
n = AddSurfaceModelsToTriangle_r( ds, model, tri2 );
if( n < 0 )
return n;
localNumSurfaceModels += n;
/* return count */
return localNumSurfaceModels;
}
/*
AddSurfaceModels()
adds a surface's shader models to the surface
*/
int AddSurfaceModels( mapDrawSurface_t *ds )
{
surfaceModel_t *model;
int i, x, y, n, pw[ 5 ], r, localNumSurfaceModels, iterations;
mesh_t src, *mesh, *subdivided;
bspDrawVert_t centroid, *tri[ 3 ];
float alpha;
/* dummy check */
if( ds == NULL || ds->shaderInfo == NULL || ds->shaderInfo->surfaceModel == NULL )
return 0;
/* init */
localNumSurfaceModels = 0;
/* walk the model list */
for( model = ds->shaderInfo->surfaceModel; model != NULL; model = model->next )
{
/* switch on type */
switch( ds->type )
{
/* handle brush faces and decals */
case SURFACE_FACE:
case SURFACE_DECAL:
/* calculate centroid */
memset( &centroid, 0, sizeof( centroid ) );
alpha = 0.0f;
/* walk verts */
for( i = 0; i < ds->numVerts; i++ )
{
VectorAdd( centroid.xyz, ds->verts[ i ].xyz, centroid.xyz );
VectorAdd( centroid.normal, ds->verts[ i ].normal, centroid.normal );
centroid.st[ 0 ] += ds->verts[ i ].st[ 0 ];
centroid.st[ 1 ] += ds->verts[ i ].st[ 1 ];
alpha += ds->verts[ i ].color[ 0 ][ 3 ];
}
/* average */
centroid.xyz[ 0 ] /= ds->numVerts;
centroid.xyz[ 1 ] /= ds->numVerts;
centroid.xyz[ 2 ] /= ds->numVerts;
if( VectorNormalize( centroid.normal, centroid.normal ) == 0.0f )
VectorCopy( ds->verts[ 0 ].normal, centroid.normal );
centroid.st[ 0 ] /= ds->numVerts;
centroid.st[ 1 ] /= ds->numVerts;
alpha /= ds->numVerts;
centroid.color[ 0 ][ 0 ] = 0xFF;
centroid.color[ 0 ][ 1 ] = 0xFF;
centroid.color[ 0 ][ 2 ] = 0xFF;
centroid.color[ 0 ][ 2 ] = (alpha > 255.0f ? 0xFF : alpha);
/* head vert is centroid */
tri[ 0 ] = &centroid;
/* walk fanned triangles */
for( i = 0; i < ds->numVerts; i++ )
{
/* set triangle */
tri[ 1 ] = &ds->verts[ i ];
tri[ 2 ] = &ds->verts[ (i + 1) % ds->numVerts ];
/* create models */
n = AddSurfaceModelsToTriangle_r( ds, model, tri );
if( n < 0 )
return n;
localNumSurfaceModels += n;
}
break;
/* handle patches */
case SURFACE_PATCH:
/* subdivide the surface */
src.width = ds->patchWidth;
src.height = ds->patchHeight;
src.verts = ds->verts;
//% subdivided = SubdivideMesh( src, 8.0f, 512 );
iterations = IterationsForCurve( ds->longestCurve, patchSubdivisions );
subdivided = SubdivideMesh2( src, iterations );
/* fit it to the curve and remove colinear verts on rows/columns */
PutMeshOnCurve( *subdivided );
mesh = RemoveLinearMeshColumnsRows( subdivided );
FreeMesh( subdivided );
/* subdivide each quad to place the models */
for( y = 0; y < (mesh->height - 1); y++ )
{
for( x = 0; x < (mesh->width - 1); x++ )
{
/* set indexes */
pw[ 0 ] = x + (y * mesh->width);
pw[ 1 ] = x + ((y + 1) * mesh->width);
pw[ 2 ] = x + 1 + ((y + 1) * mesh->width);
pw[ 3 ] = x + 1 + (y * mesh->width);
pw[ 4 ] = x + (y * mesh->width); /* same as pw[ 0 ] */
/* set radix */
r = (x + y) & 1;
/* triangle 1 */
tri[ 0 ] = &mesh->verts[ pw[ r + 0 ] ];
tri[ 1 ] = &mesh->verts[ pw[ r + 1 ] ];
tri[ 2 ] = &mesh->verts[ pw[ r + 2 ] ];
n = AddSurfaceModelsToTriangle_r( ds, model, tri );
if( n < 0 )
return n;
localNumSurfaceModels += n;
/* triangle 2 */
tri[ 0 ] = &mesh->verts[ pw[ r + 0 ] ];
tri[ 1 ] = &mesh->verts[ pw[ r + 2 ] ];
tri[ 2 ] = &mesh->verts[ pw[ r + 3 ] ];
n = AddSurfaceModelsToTriangle_r( ds, model, tri );
if( n < 0 )
return n;
localNumSurfaceModels += n;
}
}
/* free the subdivided mesh */
FreeMesh( mesh );
break;
/* handle triangle surfaces */
case SURFACE_TRIANGLES:
case SURFACE_FORCED_META:
case SURFACE_META:
/* walk the triangle list */
for( i = 0; i < ds->numIndexes; i += 3 )
{
tri[ 0 ] = &ds->verts[ ds->indexes[ i ] ];
tri[ 1 ] = &ds->verts[ ds->indexes[ i + 1 ] ];
tri[ 2 ] = &ds->verts[ ds->indexes[ i + 2 ] ];
n = AddSurfaceModelsToTriangle_r( ds, model, tri );
if( n < 0 )
return n;
localNumSurfaceModels += n;
}
break;
/* no support for flares, foghull, etc */
default:
break;
}
}
/* return count */
return localNumSurfaceModels;
}
/*
AddEntitySurfaceModels() - ydnar
adds surfacemodels to an entity's surfaces
*/
void AddEntitySurfaceModels( entity_t *e )
{
int i;
/* note it */
Sys_FPrintf( SYS_VRB, "--- AddEntitySurfaceModels ---\n" );
/* walk the surface list */
for( i = e->firstDrawSurf; i < numMapDrawSurfs; i++ )
numSurfaceModels += AddSurfaceModels( &mapDrawSurfs[ i ] );
}
/*
VolumeColorMods() - ydnar
applies brush/volumetric color/alpha modulation to vertexes
*/
static void VolumeColorMods( entity_t *e, mapDrawSurface_t *ds )
{
int i, j;
float d;
brush_t *b;
plane_t *plane;
/* early out */
if( e->colorModBrushes == NULL )
return;
/* iterate brushes */
for( b = e->colorModBrushes; b != NULL; b = b->nextColorModBrush )
{
/* worldspawn alpha brushes affect all, grouped ones only affect original entity */
if( b->entityNum != 0 && b->entityNum != ds->entityNum )
continue;
/* test bbox */
if( b->mins[ 0 ] > ds->maxs[ 0 ] || b->maxs[ 0 ] < ds->mins[ 0 ] ||
b->mins[ 1 ] > ds->maxs[ 1 ] || b->maxs[ 1 ] < ds->mins[ 1 ] ||
b->mins[ 2 ] > ds->maxs[ 2 ] || b->maxs[ 2 ] < ds->mins[ 2 ] )
continue;
/* iterate verts */
for( i = 0; i < ds->numVerts; i++ )
{
/* iterate planes */
for( j = 0; j < b->numsides; j++ )
{
/* point-plane test */
plane = &mapplanes[ b->sides[ j ].planenum ];
d = DotProduct( ds->verts[ i ].xyz, plane->normal ) - plane->dist;
if( d > 1.0f )
break;
}
/* apply colormods */
if( j == b->numsides )
ColorMod( b->contentShader->colorMod, 1, &ds->verts[ i ] );
}
}
}
/*
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( entity_t *e, tree_t *tree )
{
int i, j;
mapDrawSurface_t *ds;
shaderInfo_t *si;
vec3_t origin, mins, maxs;
int refs;
int numSurfs, numRefs, numSkyboxSurfaces;
qboolean sb;
/* note it */
Sys_FPrintf( SYS_VRB, "--- FilterDrawsurfsIntoTree ---\n" );
/* filter surfaces into the tree */
numSurfs = 0;
numRefs = 0;
numSkyboxSurfaces = 0;
for( i = e->firstDrawSurf; i < numMapDrawSurfs; i++ )
{
/* get surface and try to early out */
ds = &mapDrawSurfs[ i ];
if( ds->numVerts == 0 && ds->type != SURFACE_FLARE && ds->type != SURFACE_SHADER )
continue;
/* get shader */
si = ds->shaderInfo;
/* ydnar: skybox surfaces are special */
if( ds->skybox )
{
refs = AddReferenceToTree_r( ds, tree->headnode, qtrue );
ds->skybox = qfalse;
sb = qtrue;
}
else
{
sb = qfalse;
/* refs initially zero */
refs = 0;
/* apply texture coordinate mods */
for( j = 0; j < ds->numVerts; j++ )
TCMod( si->mod, ds->verts[ j ].st );
/* ydnar: apply shader colormod */
ColorMod( ds->shaderInfo->colorMod, ds->numVerts, ds->verts );
/* ydnar: apply brush colormod */
VolumeColorMods( e, ds );
/* ydnar: make fur surfaces */
if( si->furNumLayers > 0 )
Fur( ds );
/* ydnar/sd: make foliage surfaces */
if( si->foliage != NULL )
Foliage( ds );
/* create a flare surface if necessary */
if( si->flareShader != NULL && si->flareShader[ 0 ] )
AddSurfaceFlare( ds, e->origin );
/* ydnar: don't emit nodraw surfaces (like nodraw fog) */
if( (si->compileFlags & C_NODRAW) && ds->type != SURFACE_PATCH )
continue;
/* ydnar: bias the surface textures */
BiasSurfaceTextures( ds );
/* ydnar: globalizing of fog volume handling (eek a hack) */
if( e != entities && si->noFog == qfalse )
{
/* find surface origin and offset by entity origin */
VectorAdd( ds->mins, ds->maxs, origin );
VectorScale( origin, 0.5f, origin );
VectorAdd( origin, e->origin, origin );
VectorAdd( ds->mins, e->origin, mins );
VectorAdd( ds->maxs, e->origin, maxs );
/* set the fog number for this surface */
ds->fogNum = FogForBounds( mins, maxs, 1.0f ); //% FogForPoint( origin, 0.0f );
}
}
/* ydnar: remap shader */
if( ds->shaderInfo->remapShader && ds->shaderInfo->remapShader[ 0 ] )
ds->shaderInfo = ShaderInfoForShader( ds->shaderInfo->remapShader );
/* ydnar: gs mods: handle the various types of surfaces */
switch( ds->type )
{
/* handle brush faces */
case SURFACE_FACE:
case SURFACE_DECAL:
if( refs == 0 )
refs = FilterFaceIntoTree( ds, tree );
if( refs > 0 )
EmitFaceSurface( ds );
break;
/* handle patches */
case SURFACE_PATCH:
if( refs == 0 )
refs = FilterPatchIntoTree( ds, tree );
if( refs > 0 )
EmitPatchSurface( e, ds );
break;
/* handle triangle surfaces */
case SURFACE_TRIANGLES:
case SURFACE_FORCED_META:
case SURFACE_META:
//% Sys_FPrintf( SYS_VRB, "Surface %4d: [%1d] %4d verts %s\n", numSurfs, ds->planar, ds->numVerts, si->shader );
if( refs == 0 )
refs = FilterTrianglesIntoTree( ds, tree );
if( refs > 0 )
EmitTriangleSurface( ds );
break;
/* handle foliage surfaces (splash damage/wolf et) */
case SURFACE_FOLIAGE:
//% Sys_FPrintf( SYS_VRB, "Surface %4d: [%d] %4d verts %s\n", numSurfs, ds->numFoliageInstances, ds->numVerts, si->shader );
if( refs == 0 )
refs = FilterFoliageIntoTree( ds, tree );
if( refs > 0 )
EmitTriangleSurface( ds );
break;
/* handle foghull surfaces */
case SURFACE_FOGHULL:
if( refs == 0 )
refs = AddReferenceToTree_r( ds, tree->headnode, qfalse );
if( refs > 0 )
EmitTriangleSurface( ds );
break;
/* handle flares */
case SURFACE_FLARE:
if( refs == 0 )
refs = FilterFlareSurfIntoTree( ds, tree );
if( refs > 0 )
EmitFlareSurface( ds );
break;
/* handle shader-only surfaces */
case SURFACE_SHADER:
refs = 1;
EmitFlareSurface( ds );
break;
/* no references */
default:
refs = 0;
break;
}
/* maybe surface got marked as skybox again */
/* if we keep that flag, it will get scaled up AGAIN */
if(sb)
ds->skybox = qfalse;
/* tot up the references */
if( refs > 0 )
{
/* tot up counts */
numSurfs++;
numRefs += refs;
/* emit extra surface data */
SetSurfaceExtra( ds, numBSPDrawSurfaces - 1 );
//% Sys_FPrintf( SYS_VRB, "%d verts %d indexes\n", ds->numVerts, ds->numIndexes );
/* one last sanity check */
{
bspDrawSurface_t *out;
out = &bspDrawSurfaces[ numBSPDrawSurfaces - 1 ];
if( out->numVerts == 3 && out->numIndexes > 3 )
{
Sys_Printf( "\nWARNING: Potentially bad %s surface (%d: %d, %d)\n %s\n",
surfaceTypes[ ds->type ],
numBSPDrawSurfaces - 1, out->numVerts, out->numIndexes, si->shader );
}
}
/* ydnar: handle skybox surfaces */
if( ds->skybox )
{
MakeSkyboxSurface( ds );
numSkyboxSurfaces++;
}
}
}
/* emit some statistics */
Sys_FPrintf( SYS_VRB, "%9d references\n", numRefs );
Sys_FPrintf( SYS_VRB, "%9d (%d) emitted drawsurfs\n", numSurfs, numBSPDrawSurfaces );
Sys_FPrintf( SYS_VRB, "%9d stripped face surfaces\n", numStripSurfaces );
Sys_FPrintf( SYS_VRB, "%9d fanned face surfaces\n", numFanSurfaces );
Sys_FPrintf( SYS_VRB, "%9d maxarea'd face surfaces\n", numMaxAreaSurfaces );
Sys_FPrintf( SYS_VRB, "%9d surface models generated\n", numSurfaceModels );
Sys_FPrintf( SYS_VRB, "%9d skybox surfaces generated\n", numSkyboxSurfaces );
for( i = 0; i < NUM_SURFACE_TYPES; i++ )
Sys_FPrintf( SYS_VRB, "%9d %s surfaces\n", numSurfacesByType[ i ], surfaceTypes[ i ] );
Sys_FPrintf( SYS_VRB, "%9d redundant indexes supressed, saving %d Kbytes\n", numRedundantIndexes, (numRedundantIndexes * 4 / 1024) );
}