/* ------------------------------------------------------------------------------- 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, reverse; 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) */ /* 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++ ) { if( numBSPDrawIndexes == MAX_MAP_DRAW_INDEXES ) Error( "MAX_MAP_DRAW_INDEXES" ); 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 ] = ∣ n = AddSurfaceModelsToTriangle_r( ds, model, tri2 ); if( n < 0 ) return n; localNumSurfaceModels += n; /* recurse to second triangle */ VectorCopy( tri, tri2 ); tri2[ (max + 1) % 3 ] = ∣ 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( ¢roid, 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 ] = ¢roid; /* 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 != NULL && (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) ); }