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

1597 lines
36 KiB
C

//=======================================================================
// Copyright XashXT Group 2010 ©
// gl_rsurf.c - surface-related refresh code
//=======================================================================
#include "common.h"
#include "client.h"
#include "gl_local.h"
#include "cm_local.h"
#include "matrix_lib.h"
#define BLOCK_WIDTH 128
#define BLOCK_HEIGHT 128
typedef struct
{
qboolean lightmap_modified[MAX_LIGHTMAPS];
wrect_t lightmap_rectchange[MAX_LIGHTMAPS];
uint allocated[MAX_LIGHTMAPS][BLOCK_WIDTH];
byte lightmaps[MAX_LIGHTMAPS*BLOCK_WIDTH*BLOCK_HEIGHT*4];
} lightmap_state_t;
typedef struct gl_light_s
{
int local[2];
int minlight; // rad - minlight
int rad;
} gl_light_t;
static vec3_t modelorg; // relative to viewpoint
static vec3_t modelmins;
static vec3_t modelmaxs;
static byte visbytes[MAX_MAP_LEAFS/8];
static uint r_blockLights[BLOCK_WIDTH*BLOCK_HEIGHT*3];
static glpoly_t *lightmap_polys[MAX_LIGHTMAPS];
static glpoly_t *fullbright_polys[MAX_TEXTURES];
static qboolean draw_fullbrights = false;
static lightmap_state_t r_lmState;
static msurface_t *skychain = NULL;
static msurface_t *waterchain = NULL;
static gl_light_t r_dlights[MAX_DLIGHTS];
static int r_numdlights;
static void R_BuildLightMap( msurface_t *surf, byte *dest, int stride );
static void LM_UploadBlock( int lightmapnum );
byte *Mod_GetCurrentVis( void )
{
return Mod_LeafPVS( r_viewleaf, cl.worldmodel );
}
static void BoundPoly( int numverts, float *verts, vec3_t mins, vec3_t maxs )
{
int i, j;
float *v;
mins[0] = mins[1] = mins[2] = 9999;
maxs[0] = maxs[1] = maxs[2] = -9999;
for( i = 0, v = verts; i < numverts; i++ )
{
for( j = 0; j < 3; j++, v++ )
{
if( *v < mins[j] ) mins[j] = *v;
if( *v > maxs[j] ) maxs[j] = *v;
}
}
}
static void SubdividePolygon_r( msurface_t *warpface, int numverts, float *verts )
{
int i, j, k, f, b;
vec3_t mins, maxs;
float m, frac, s, t, *v;
vec3_t front[SUBDIVIDE_SIZE], back[SUBDIVIDE_SIZE], total;
float dist[SUBDIVIDE_SIZE], total_s, total_t;
glpoly_t *poly;
if( numverts > ( SUBDIVIDE_SIZE - 4 ))
Host_Error( "Mod_SubdividePolygon: too many vertexes on face ( %i )\n", numverts );
BoundPoly( numverts, verts, mins, maxs );
for( i = 0; i < 3; i++ )
{
m = ( mins[i] + maxs[i] ) * 0.5f;
m = SUBDIVIDE_SIZE * floor( m / SUBDIVIDE_SIZE + 0.5f );
if( maxs[i] - m < 8 ) continue;
if( m - mins[i] < 8 ) continue;
// cut it
v = verts + i;
for( j = 0; j < numverts; j++, v += 3 )
dist[j] = *v - m;
// wrap cases
dist[j] = dist[0];
v -= i;
VectorCopy( verts, v );
f = b = 0;
v = verts;
for( j = 0; j < numverts; j++, v += 3 )
{
if( dist[j] >= 0 )
{
VectorCopy( v, front[f] );
f++;
}
if( dist[j] <= 0 )
{
VectorCopy (v, back[b]);
b++;
}
if( dist[j] == 0 || dist[j+1] == 0 )
continue;
if(( dist[j] > 0 ) != ( dist[j+1] > 0 ))
{
// clip point
frac = dist[j] / ( dist[j] - dist[j+1] );
for( k = 0; k < 3; k++ )
front[f][k] = back[b][k] = v[k] + frac * (v[3+k] - v[k]);
f++;
b++;
}
}
SubdividePolygon_r( warpface, f, front[0] );
SubdividePolygon_r( warpface, b, back[0] );
return;
}
// add a point in the center to help keep warp valid
poly = Mem_Alloc( loadmodel->mempool, sizeof( glpoly_t ) + ((numverts-4)+2) * VERTEXSIZE * sizeof( float ));
poly->next = warpface->polys;
poly->flags = warpface->flags;
warpface->polys = poly;
poly->numverts = numverts + 2;
VectorClear( total );
total_s = 0;
total_t = 0;
for( i = 0; i < numverts; i++, verts += 3 )
{
VectorCopy( verts, poly->verts[i+1] );
s = DotProduct( verts, warpface->texinfo->vecs[0] );
t = DotProduct( verts, warpface->texinfo->vecs[1] );
total_s += s;
total_t += t;
VectorAdd( total, verts, total );
poly->verts[i+1][3] = s;
poly->verts[i+1][4] = t;
}
VectorScale( total, ( 1.0f / numverts ), poly->verts[0] );
poly->verts[0][3] = total_s / numverts;
poly->verts[0][4] = total_t / numverts;
// copy first vertex to last
Mem_Copy( poly->verts[i+1], poly->verts[1], sizeof( poly->verts[0] ));
}
/*
================
GL_SubdivideSurface
Breaks a polygon up along axial 64 unit
boundaries so that turbulent and sky warps
can be done reasonably.
================
*/
void GL_SubdivideSurface( msurface_t *fa )
{
vec3_t verts[SUBDIVIDE_SIZE];
int numverts;
int i, lindex;
float *vec;
// convert edges back to a normal polygon
numverts = 0;
for( i = 0; i < fa->numedges; i++ )
{
lindex = loadmodel->surfedges[fa->firstedge + i];
if( lindex > 0 ) vec = loadmodel->vertexes[loadmodel->edges[lindex].v[0]].position;
else vec = loadmodel->vertexes[loadmodel->edges[-lindex].v[1]].position;
VectorCopy (vec, verts[numverts]);
numverts++;
}
// do subdivide
SubdividePolygon_r( fa, numverts, verts[0] );
}
/*
================
GL_BuildPolygonFromSurface
================
*/
void GL_BuildPolygonFromSurface( msurface_t *fa )
{
int i, lindex, lnumverts;
medge_t *pedges, *r_pedge;
int vertpage;
float *vec;
float s, t;
glpoly_t *poly;
// already created
if( fa->polys ) return;
// reconstruct the polygon
pedges = loadmodel->edges;
lnumverts = fa->numedges;
vertpage = 0;
// draw texture
poly = Mem_Alloc( loadmodel->mempool, sizeof( glpoly_t ) + ( lnumverts - 4 ) * VERTEXSIZE * sizeof( float ));
poly->next = fa->polys;
poly->flags = fa->flags;
fa->polys = poly;
poly->numverts = lnumverts;
for( i = 0; i < lnumverts; i++ )
{
lindex = loadmodel->surfedges[fa->firstedge + i];
if( lindex > 0 )
{
r_pedge = &pedges[lindex];
vec = loadmodel->vertexes[r_pedge->v[0]].position;
}
else
{
r_pedge = &pedges[-lindex];
vec = loadmodel->vertexes[r_pedge->v[1]].position;
}
s = DotProduct( vec, fa->texinfo->vecs[0] ) + fa->texinfo->vecs[0][3];
s /= fa->texinfo->texture->width;
t = DotProduct( vec, fa->texinfo->vecs[1] ) + fa->texinfo->vecs[1][3];
t /= fa->texinfo->texture->height;
VectorCopy( vec, poly->verts[i] );
poly->verts[i][3] = s;
poly->verts[i][4] = t;
// lightmap texture coordinates
s = DotProduct( vec, fa->texinfo->vecs[0] ) + fa->texinfo->vecs[0][3];
s -= fa->texturemins[0];
s += fa->light_s * 16;
s += 8;
s /= BLOCK_WIDTH * 16; //fa->texinfo->texture->width;
t = DotProduct( vec, fa->texinfo->vecs[1] ) + fa->texinfo->vecs[1][3];
t -= fa->texturemins[1];
t += fa->light_t * 16;
t += 8;
t /= BLOCK_HEIGHT * 16; //fa->texinfo->texture->height;
poly->verts[i][5] = s;
poly->verts[i][6] = t;
}
}
/*
===============
R_TextureAnimation
Returns the proper texture for a given time and base texture
===============
*/
texture_t *R_TextureAnimation( texture_t *base )
{
int reletive;
int count, speed;
if( RI.currententity->curstate.frame )
{
if( base->alternate_anims )
base = base->alternate_anims;
}
if( !base->anim_total )
return base;
// GoldSrc and Quake1 has different animating speed
if( world.version == Q1BSP_VERSION )
speed = 10;
else speed = 20;
reletive = (int)(cl.time * speed) % base->anim_total;
count = 0;
while( base->anim_min > reletive || base->anim_max <= reletive )
{
base = base->anim_next;
if( !base ) Host_Error( "R_TextureAnimation: broken loop\n" );
if( ++count > 100 ) Host_Error( "R_TextureAnimation: infinite loop\n" );
}
return base;
}
/*
================
DrawGLWaterPoly
Warp the vertex coordinates
================
*/
void DrawGLWaterPoly( glpoly_t *p, qboolean lightmap )
{
float *v;
vec3_t nv;
int i;
GL_CleanUpTextureUnits( 1 );
pglBegin( GL_TRIANGLE_FAN );
v = p->verts[0];
for( i = 0; i < p->numverts; i++, v += VERTEXSIZE )
{
if( lightmap )
pglTexCoord2f( v[5], v[6] );
else pglTexCoord2f( v[3], v[4] );
nv[0] = v[0] + 8 * com.sin( v[1] * 0.05f + cl.time ) * com.sin( v[2] * 0.05f + cl.time );
nv[1] = v[1] + 8 * com.sin( v[0] * 0.05f + cl.time ) * com.sin( v[2] * 0.05f + cl.time );
nv[2] = v[2];
pglVertex3fv( nv );
}
pglEnd();
}
/*
================
DrawGLPoly
================
*/
void DrawGLPoly( glpoly_t *p )
{
float *v;
float sOffset;
float tOffset;
cl_entity_t *e = RI.currententity;
int i;
if( p->flags & SURF_CONVEYOR )
{
gltexture_t *texture;
float flConveyorSpeed;
float flRate, flAngle;
flConveyorSpeed = (e->curstate.rendercolor.g<<8|e->curstate.rendercolor.b) / 16.0f;
if( e->curstate.rendercolor.r ) flConveyorSpeed = -flConveyorSpeed;
texture = R_GetTexture( glState.currentTextures[glState.activeTMU] );
flRate = abs( flConveyorSpeed ) / (float)texture->srcWidth;
flAngle = ( flConveyorSpeed >= 0 ) ? 180 : 0;
sOffset = RI.refdef.time * com.cos( flAngle * ( M_PI / 180.0f )) * flRate;
tOffset = RI.refdef.time * com.sin( flAngle * ( M_PI / 180.0f )) * flRate;
// make sure that we are positive
if( sOffset < 0.0f ) sOffset += 1.0f + -(int)sOffset;
if( tOffset < 0.0f ) tOffset += 1.0f + -(int)tOffset;
// make sure that we are in a [0,1] range
sOffset = sOffset - (int)sOffset;
tOffset = tOffset - (int)tOffset;
}
else
{
sOffset = tOffset = 0.0f;
}
pglBegin( GL_POLYGON );
v = p->verts[0];
for( i = 0; i < p->numverts; i++, v += VERTEXSIZE )
{
pglTexCoord2f( v[3] + sOffset, v[4] + tOffset );
pglVertex3fv( v );
}
pglEnd();
}
/*
================
R_BlendLightmaps
================
*/
void R_BlendLightmaps( void )
{
int i, j;
glpoly_t *p;
float *v;
if( r_fullbright->integer || !cl.worldmodel->lightdata )
return;
if( RI.currententity )
{
// check for rendermode
switch( RI.currententity->curstate.rendermode )
{
case kRenderTransTexture:
case kRenderTransInverse:
case kRenderTransAdd:
case kRenderGlow:
return; // no lightmaps
}
}
if( !r_lightmap->integer )
{
GL_TexEnv( GL_MODULATE );
GL_SetState( GLSTATE_SRCBLEND_ZERO|GLSTATE_DSTBLEND_SRC_COLOR|GLSTATE_DEPTHFUNC_EQ );
}
for( i = 0; i < MAX_LIGHTMAPS; i++ )
{
p = lightmap_polys[i];
if( !p ) continue;
GL_Bind( GL_TEXTURE0, tr.lightmapTextures[i] );
if( r_lmState.lightmap_modified[i] )
LM_UploadBlock( i );
for( ; p; p = p->chain )
{
pglBegin( GL_POLYGON );
v = p->verts[0];
for( j = 0; j < p->numverts; j++, v += VERTEXSIZE )
{
pglTexCoord2f( v[5], v[6] );
pglVertex3fv( v );
}
pglEnd();
}
}
GL_TexEnv( GL_REPLACE );
GL_SetState( GLSTATE_DEPTHWRITE );
}
/*
================
R_RenderFullbrights
================
*/
void R_RenderFullbrights( void )
{
glpoly_t *p;
int i;
if( !draw_fullbrights )
return;
GL_SetState( GLSTATE_SRCBLEND_ONE|GLSTATE_DSTBLEND_ONE );
GL_TexEnv( GL_MODULATE );
for( i = 1; i < MAX_TEXTURES; i++ )
{
if( !fullbright_polys[i] )
continue;
GL_Bind( GL_TEXTURE0, i );
#if 0
for( p = fullbright_polys[i]; p; p = p->fb_chain )
{
if( p->flags & SURF_DRAWTURB )
EmitWaterPolys( p );
else DrawGLPoly( p );
}
#endif
fullbright_polys[i] = NULL;
}
GL_SetState( GLSTATE_DEPTHWRITE );
draw_fullbrights = false;
}
/*
===============
R_BuildDlights
===============
*/
void R_BuildDlights( msurface_t *surf )
{
float dist;
vec3_t impact;
int lnum, smax, tmax;
int tdmin, sdmin, distmin;
int irad, iminlight, local[2];
vec3_t origin_l;
matrix4x4 imatrix;
gl_light_t *light;
mtexinfo_t *tex;
dlight_t *dl;
r_numdlights = 0;
smax = ( surf->extents[0] >> 4 ) + 1;
tmax = ( surf->extents[1] >> 4 ) + 1;
tex = surf->texinfo;
for( lnum = 0; lnum < MAX_DLIGHTS; lnum++ )
{
if(!( surf->dlightbits & ( 1<<lnum )))
continue; // not lit by this light
dl = &cl_dlights[lnum];
if( !tr.modelviewIdentity )
{
// transform light origin to local bmodel space
Matrix4x4_Invert_Simple( imatrix, RI.objectMatrix );
Matrix4x4_VectorTransform( imatrix, dl->origin, origin_l );
}
else VectorCopy( dl->origin, origin_l );
dist = PlaneDiff( origin_l, surf->plane );
irad = ( dl->radius - fabs( dist )) * 256;
iminlight = dl->minlight * 256;
if( irad < iminlight ) continue;
iminlight = irad - iminlight;
VectorMA( origin_l, -dist, surf->plane->normal, impact );
local[0] = DotProduct( impact, tex->vecs[0] ) + tex->vecs[0][3] - surf->texturemins[0];
local[1] = DotProduct( impact, tex->vecs[1] ) + tex->vecs[1][3] - surf->texturemins[1];
// check if this dlight will touch the surface
if( local[1] > 0 )
{
tdmin = local[1] - (tmax << 4);
if( tdmin < 0 ) tdmin = 0;
}
else
{
tdmin = -local[1];
}
if( local[0] > 0 )
{
sdmin = local[0] - (smax << 4);
if( sdmin < 0 ) sdmin = 0;
}
else
{
sdmin = -local[0];
}
distmin = (sdmin > tdmin) ? (sdmin << 8) + (tdmin << 7) : (tdmin << 8) + (sdmin << 7);
if( distmin < iminlight )
{
// save dlight info
light = &r_dlights[r_numdlights];
light->minlight = iminlight;
light->local[0] = local[0];
light->local[1] = local[1];
light->rad = irad;
r_numdlights++;
}
}
}
/*
================
R_RenderDynamicLightmaps
================
*/
void R_RenderDynamicLightmaps( msurface_t *fa )
{
byte *base;
wrect_t *rect;
uint scale;
qboolean lightstyle_modified = false;
int maps, smax, tmax;
if( !cl.worldmodel->lightdata ) return;
if( fa->flags & SURF_DRAWTILED )
return;
fa->polys->chain = lightmap_polys[fa->lightmaptexturenum];
lightmap_polys[fa->lightmaptexturenum] = fa->polys;
if( !r_dynamic->integer )
return;
// check for lightmap modification
for( maps = 0; maps < MAXLIGHTMAPS && fa->styles[maps] != 255; maps++ )
{
scale = RI.lightstylevalue[fa->styles[maps]];
if( scale != fa->cached_light[maps] )
{
lightstyle_modified = true;
break;
}
}
if( fa->dlightframe == tr.framecount )
R_BuildDlights( fa );
else r_numdlights = 0;
if( !r_numdlights && !fa->cached_dlight && !lightstyle_modified )
return;
r_lmState.lightmap_modified[fa->lightmaptexturenum] = true;
rect = &r_lmState.lightmap_rectchange[fa->lightmaptexturenum];
if( fa->light_t < rect->right )
{
if( rect->bottom )
rect->bottom += rect->right - fa->light_t;
rect->right = fa->light_t;
}
if( fa->light_s < rect->left )
{
if( rect->top )
rect->top += rect->left - fa->light_s;
rect->left = fa->light_s;
}
smax = ( fa->extents[0] >> 4 ) + 1;
tmax = ( fa->extents[1] >> 4 ) + 1;
if(( rect->top + rect->left ) < ( fa->light_s + smax ))
rect->top = ( fa->light_s - rect->left ) + smax;
if(( rect->bottom + rect->right ) < ( fa->light_t + tmax ))
rect->bottom = ( fa->light_t - rect->right ) + tmax;
base = r_lmState.lightmaps + fa->lightmaptexturenum * BLOCK_WIDTH * BLOCK_HEIGHT * 4;
base += fa->light_t * BLOCK_WIDTH * 4 + fa->light_s * 4;
R_BuildLightMap( fa, base, BLOCK_WIDTH * 4 );
}
/*
================
R_RenderBrushPoly
================
*/
void R_RenderBrushPoly( msurface_t *fa )
{
texture_t *t;
r_stats.c_brush_polys++;
if( fa->flags & SURF_DRAWSKY )
{
if( world.version == Q1BSP_VERSION )
{
// warp texture, no lightmaps
EmitSkyLayers( fa );
}
return;
}
t = R_TextureAnimation( fa->texinfo->texture );
GL_Bind( GL_TEXTURE0, t->gl_texturenum );
#if 0
if( t->fb_texturenum )
{
fa->polys->fb_chain = fullbright_polys[t->fb_texturenum];
fullbright_polys[t->fb_texturenum] = fa->polys;
draw_fullbrights = true;
}
#endif
if( fa->flags & SURF_DRAWTURB )
{
// warp texture, no lightmaps
EmitWaterPolys( fa->polys );
return;
}
DrawGLPoly( fa->polys );
// add the poly to the proper lightmap chain
R_RenderDynamicLightmaps( fa );
}
/*
===============
R_AddDynamicLights
===============
*/
void R_AddDynamicLights( msurface_t *surf )
{
color24 color;
int local[2];
int i, sd, td, s, t, val;
int irad, idist, iminlight;
int smax, tmax, tmp;
gl_light_t *light;
uint *bl;
smax = (surf->extents[0] >> 4) + 1;
tmax = (surf->extents[1] >> 4) + 1;
for( i = 0, light = r_dlights; i < r_numdlights; i++, light++ )
{
color = cl_dlights[i].color;
iminlight = light->minlight;
irad = light->rad;
local[1] = light->local[1];
bl = r_blockLights;
for( t = 0; t < tmax; t++ )
{
td = (local[1] < 0) ? -local[1] : local[1];
local[1] -= LM_SAMPLE_SIZE;
local[0] = light->local[0];
for( s = 0; s < smax; s++ )
{
sd = (local[0] < 0) ? -local[0] : local[0];
local[0] -= LM_SAMPLE_SIZE;
idist = (sd > td) ? (sd << 8) + (td << 7) : (td << 8) + (sd << 7);
if( idist < iminlight )
{
if( cl_dlights[i].dark )
tmp = (idist - irad) >> 7;
else tmp = (irad - idist) >> 7;
// catch negative lights (for dark mode)
val = bl[0] + tmp * color.r;
bl[0] = max( 0, val );
val = bl[1] + tmp * color.g;
bl[1] = max( 0, val );
val = bl[2] + tmp * color.b;
bl[2] = max( 0, val );
}
bl += 3;
}
}
}
}
/*
================
R_DrawTextureChains
================
*/
void R_DrawTextureChains( void )
{
int i;
msurface_t *s;
texture_t *t;
// make sure what color is reset
pglColor4ub( 255, 255, 255, 255 );
// clip skybox surfaces
for( s = skychain; s != NULL; s = s->texturechain )
R_AddSkyBoxSurface( s );
for( i = 0; i < cl.worldmodel->numtextures; i++ )
{
t = cl.worldmodel->textures[i];
if( !t ) continue;
s = t->texturechain;
if( !s ) continue;
if( i == tr.skytexturenum )
{
if( world.version == Q1BSP_VERSION )
R_DrawSkyChain( s );
}
else
{
for( ; s != NULL; s = s->texturechain )
R_RenderBrushPoly( s );
}
t->texturechain = NULL;
}
}
/*
================
R_DrawWaterSurfaces
================
*/
void R_DrawWaterSurfaces( void )
{
int i;
msurface_t *s;
texture_t *t;
// non-transparent water is already drawed
if( r_wateralpha->value >= 1.0f )
return;
// go back to the world matrix
pglMatrixMode( GL_MODELVIEW );
GL_LoadMatrix( RI.worldviewMatrix );
GL_SetState( GLSTATE_SRCBLEND_SRC_ALPHA|GLSTATE_DSTBLEND_ONE_MINUS_SRC_ALPHA );
pglColor4f( 1.0f, 1.0f, 1.0f, r_wateralpha->value );
GL_TexEnv( GL_MODULATE );
for( i = 0; i < cl.worldmodel->numtextures; i++ )
{
t = cl.worldmodel->textures[i];
if( !t ) continue;
s = t->texturechain;
if( !s ) continue;
if(!( s->flags & SURF_DRAWTURB ))
continue;
// set modulate mode explicitly
GL_Bind( GL_TEXTURE0, t->gl_texturenum );
for( ; s; s = s->texturechain )
EmitWaterPolys( s->polys );
t->texturechain = NULL;
}
GL_SetState( GLSTATE_DEPTHWRITE );
pglColor4f( 1.0f, 1.0f, 1.0f, 1.0f );
GL_TexEnv( GL_REPLACE );
}
/*
=================
R_SurfaceCompare
compare translucent surfaces
=================
*/
static int R_SurfaceCompare( const msurface_t **a, const msurface_t **b )
{
msurface_t *surf1, *surf2;
mextrasurf_t *info1, *info2;
vec3_t vecLength, org1, org2;
float len1, len2;
surf1 = (msurface_t *)*a;
surf2 = (msurface_t *)*b;
info1 = SURF_INFO( surf1, RI.currentmodel );
info2 = SURF_INFO( surf2, RI.currentmodel );
VectorAdd( RI.currententity->origin, info1->origin, org1 );
VectorAdd( RI.currententity->origin, info2->origin, org2 );
VectorSubtract( RI.pvsorigin, org1, vecLength );
len1 = VectorLength( vecLength );
VectorSubtract( RI.pvsorigin, org2, vecLength );
len2 = VectorLength( vecLength );
if( len1 > len2 )
return -1;
if( len1 < len2 )
return 1;
return 0;
}
static _inline qboolean R_CullSurface( msurface_t *surf, uint clipflags )
{
mextrasurf_t *info;
if( surf->flags & SURF_WATERCSG && !( RI.currententity->curstate.effects & EF_NOWATERCSG ))
return true;
if( r_nocull->integer )
return false;
// world surfaces can be culled by vis frame too
if( RI.currententity == clgame.entities && surf->visframe != tr.framecount )
return true;
if( r_faceplanecull->integer && glState.faceCull != 0 )
{
if(!(surf->flags & SURF_DRAWTURB) || !RI.currentWaveHeight )
{
if( !VectorIsNull( surf->plane->normal ))
{
float dist;
dist = PlaneDiff( modelorg, surf->plane );
if( glState.faceCull == GL_FRONT || ( RI.params & RP_MIRRORVIEW ))
{
if( surf->flags & SURF_PLANEBACK )
{
if( dist >= -BACKFACE_EPSILON )
return true; // wrong side
}
else
{
if( dist <= BACKFACE_EPSILON )
return true; // wrong side
}
}
else if( glState.faceCull == GL_BACK )
{
if( surf->flags & SURF_PLANEBACK )
{
if( dist <= BACKFACE_EPSILON )
return true; // wrong side
}
else
{
if( dist >= -BACKFACE_EPSILON )
return true; // wrong side
}
}
}
}
}
info = SURF_INFO( surf, RI.currentmodel );
return ( clipflags && R_CullBox( info->mins, info->maxs, clipflags ));
}
/*
=================
R_DrawBrushModel
=================
*/
void R_DrawBrushModel( cl_entity_t *e )
{
int i, k, num_sorted;
qboolean need_sort = false;
vec3_t mins, maxs;
msurface_t *psurf;
model_t *clmodel;
qboolean rotated;
dlight_t *l;
clmodel = e->model;
RI.currentWaveHeight = RI.currententity->curstate.scale * 32.0f;
if( !VectorIsNull( e->angles ))
{
for( i = 0; i < 3; i++ )
{
mins[i] = e->origin[i] - clmodel->radius;
maxs[i] = e->origin[i] + clmodel->radius;
}
rotated = true;
}
else
{
VectorAdd( e->origin, clmodel->mins, mins );
VectorAdd( e->origin, clmodel->maxs, maxs );
rotated = false;
}
if( R_CullBox( mins, maxs, RI.clipFlags ))
return;
Mem_Set( lightmap_polys, 0, sizeof( lightmap_polys ));
if( rotated ) R_RotateForEntity( e );
else R_TranslateForEntity( e );
VectorSubtract( RI.cullorigin, e->origin, modelorg );
e->visframe = tr.framecount; // visible
if( rotated )
{
vec3_t temp;
VectorCopy( modelorg, temp );
Matrix4x4_VectorITransform( RI.objectMatrix, temp, modelorg );
}
// calculate dynamic lighting for bmodel if it's not an
// instanced model
if( clmodel->firstmodelsurface != 0 )
{
vec3_t origin_l, oldorigin;
matrix4x4 imatrix;
for( k = 0, l = cl_dlights; k < MAX_DLIGHTS; k++, l++ )
{
if( l->die < cl.time || !l->radius )
continue;
VectorCopy( l->origin, oldorigin ); // save oldorigin
Matrix4x4_Invert_Simple( imatrix, RI.objectMatrix );
Matrix4x4_VectorTransform( imatrix, l->origin, origin_l );
VectorCopy( origin_l, l->origin ); // move light in bmodel space
R_MarkLights( l, 1<<k, clmodel->nodes + clmodel->hulls[0].firstclipnode );
VectorCopy( oldorigin, l->origin ); // restore lightorigin
}
}
// setup the rendermode
GL_SetRenderMode( e->curstate.rendermode );
// setup the color and alpha
switch( e->curstate.rendermode )
{
case kRenderTransAdd:
case kRenderTransTexture:
case kRenderTransInverse:
need_sort = true;
case kRenderTransAlpha:
case kRenderGlow:
pglColor4ub( 255, 255, 255, e->curstate.renderamt );
break;
case kRenderTransColor:
pglDisable( GL_TEXTURE_2D );
pglColor4ub( e->curstate.rendercolor.r, e->curstate.rendercolor.g,
e->curstate.rendercolor.b, e->curstate.renderamt );
break;
default:
pglColor4ub( 255, 255, 255, 255 );
break;
}
num_sorted = 0;
psurf = &clmodel->surfaces[clmodel->firstmodelsurface];
for( i = 0; i < clmodel->nummodelsurfaces; i++, psurf++ )
{
if( R_CullSurface( psurf, 0 ))
continue;
if( need_sort )
{
world.draw_surfaces[num_sorted] = psurf;
num_sorted++;
ASSERT( world.max_surfaces >= num_sorted );
}
else
{
// render unsorted (solid)
R_RenderBrushPoly( psurf );
}
}
if( need_sort )
qsort( world.draw_surfaces, num_sorted, sizeof( msurface_t* ), R_SurfaceCompare );
// draw sorted translucent surfaces
for( i = 0; i < num_sorted; i++ )
R_RenderBrushPoly( world.draw_surfaces[i] );
if( e->curstate.rendermode == kRenderTransColor )
pglEnable( GL_TEXTURE_2D );
R_BlendLightmaps();
R_LoadIdentity(); // restore worldmatrix
}
/*
=================
R_DrawStaticModel
Merge static model brushes with world surfaces
=================
*/
void R_DrawStaticModel( cl_entity_t *e )
{
int i, k;
model_t *clmodel;
msurface_t *psurf;
dlight_t *l;
clmodel = e->model;
if( R_CullBox( clmodel->mins, clmodel->maxs, RI.clipFlags ))
return;
// calculate dynamic lighting for bmodel if it's not an
// instanced model
if( clmodel->firstmodelsurface != 0 )
{
for( k = 0, l = cl_dlights; k < MAX_DLIGHTS; k++, l++ )
{
if( l->die < cl.time || !l->radius )
continue;
R_MarkLights( l, 1<<k, clmodel->nodes + clmodel->hulls[0].firstclipnode );
}
}
psurf = &clmodel->surfaces[clmodel->firstmodelsurface];
for( i = 0; i < clmodel->nummodelsurfaces; i++, psurf++ )
{
if( R_CullSurface( psurf, 0 ))
continue;
psurf->texturechain = psurf->texinfo->texture->texturechain;
psurf->texinfo->texture->texturechain = psurf;
}
}
/*
=================
R_DrawStaticBrushes
Insert static brushes into world texture chains
=================
*/
void R_DrawStaticBrushes( void )
{
int i;
// draw static entities
for( i = 0; i < tr.num_static_entities; i++ )
{
if( RI.refdef.onlyClientDraw )
break;
RI.currententity = tr.static_entities[i];
RI.currentmodel = RI.currententity->model;
ASSERT( RI.currententity != NULL );
ASSERT( RI.currententity->model != NULL );
switch( RI.currententity->model->type )
{
case mod_brush:
R_DrawStaticModel( RI.currententity );
break;
default:
Host_Error( "R_DrawStatics: non bsp model in static list!\n" );
break;
}
}
}
/*
=============================================================
WORLD MODEL
=============================================================
*/
/*
================
R_RecursiveWorldNode
================
*/
void R_RecursiveWorldNode( mnode_t *node, uint clipflags )
{
const mplane_t *clipplane;
int i, clipped;
msurface_t *surf, **mark;
mleaf_t *pleaf;
int c, side;
float dot;
if( node->contents == CONTENTS_SOLID )
return; // hit a solid leaf
if( node->visframe != tr.visframecount )
return;
if( clipflags )
{
for( i = 0, clipplane = RI.frustum; i < 6; i++, clipplane++ )
{
if(!( clipflags & ( 1<<i )))
continue;
clipped = BoxOnPlaneSide( node->minmaxs, node->minmaxs + 3, clipplane );
if( clipped == 2 ) return;
if( clipped == 1 ) clipflags &= ~(1<<i);
}
}
// if a leaf node, draw stuff
if( node->contents < 0 )
{
pleaf = (mleaf_t *)node;
mark = pleaf->firstmarksurface;
c = pleaf->nummarksurfaces;
if( c )
{
do
{
(*mark)->visframe = tr.framecount;
mark++;
} while( --c );
}
// deal with model fragments in this leaf
if( pleaf->efrags )
R_StoreEfrags( &pleaf->efrags );
r_stats.c_world_leafs++;
return;
}
// node is just a decision point, so go down the apropriate sides
// find which side of the node we are on
dot = PlaneDiff( modelorg, node->plane );
side = (dot >= 0) ? 0 : 1;
// recurse down the children, front side first
R_RecursiveWorldNode( node->children[side], clipflags );
// draw stuff
for( c = node->numsurfaces, surf = cl.worldmodel->surfaces + node->firstsurface; c; c--, surf++ )
{
if( R_CullSurface( surf, clipflags ))
continue;
if( surf->flags & SURF_DRAWSKY && world.version == HLBSP_VERSION )
{
// make sky chain to right clip the skybox
surf->texturechain = skychain;
skychain = surf;
}
else
{
surf->texturechain = surf->texinfo->texture->texturechain;
surf->texinfo->texture->texturechain = surf;
}
}
// recurse down the back side
R_RecursiveWorldNode( node->children[!side], clipflags );
}
/*
=============
R_DrawWorld
=============
*/
void R_DrawWorld( void )
{
RI.currententity = clgame.entities;
RI.currentmodel = RI.currententity->model;
if( !RI.drawWorld || RI.refdef.onlyClientDraw )
return;
VectorCopy( RI.cullorigin, modelorg );
Mem_Set( lightmap_polys, 0, sizeof( lightmap_polys ));
Mem_Set( fullbright_polys, 0, sizeof( fullbright_polys ));
RI.currentWaveHeight = RI.waveHeight;
GL_SetRenderMode( kRenderNormal );
ClearBounds( RI.visMins, RI.visMaxs );
R_ClearSkyBox ();
R_RecursiveWorldNode( cl.worldmodel->nodes, RI.clipFlags );
R_DrawStaticBrushes();
R_DrawTextureChains();
R_BlendLightmaps();
R_RenderFullbrights();
if( skychain )
R_DrawSkyBox();
skychain = NULL;
}
/*
===============
R_MarkLeaves
Mark the leaves and nodes that are in the PVS for the current leaf
===============
*/
void R_MarkLeaves( void )
{
byte *vis;
mnode_t *node;
int i;
if( !RI.drawWorld ) return;
if( r_viewleaf == r_oldviewleaf && r_viewleaf2 == r_oldviewleaf2 && !r_novis->integer && r_viewleaf != NULL )
return;
// development aid to let you run around
// and see exactly where the pvs ends
if( r_lockpvs->integer )
return;
tr.visframecount++;
r_oldviewleaf = r_viewleaf;
r_oldviewleaf2 = r_viewleaf2;
if( r_novis->integer || r_viewleaf == NULL || !cl.worldmodel->visdata )
{
// force to get full visibility
vis = Mod_LeafPVS( NULL, NULL );
}
else
{
// may have to combine two clusters
// because of solid water boundaries
vis = Mod_LeafPVS( r_viewleaf, cl.worldmodel );
if( r_viewleaf != r_viewleaf2 )
{
int longs = ( cl.worldmodel->numleafs + 31 ) >> 5;
Mem_Copy( visbytes, vis, longs << 2 );
vis = Mod_LeafPVS( r_viewleaf2, cl.worldmodel );
for( i = 0; i < longs; i++ )
((int *)visbytes)[i] |= ((int *)vis)[i];
vis = visbytes;
}
}
for( i = 0; i < cl.worldmodel->numleafs; i++ )
{
if( vis[i>>3] & ( 1<<( i & 7 )))
{
node = (mnode_t *)&cl.worldmodel->leafs[i+1];
do
{
if( node->visframe == tr.visframecount )
break;
node->visframe = tr.visframecount;
node = node->parent;
} while( node );
}
}
}
/*
=============================================================================
LIGHTMAP ALLOCATION
=============================================================================
*/
static void LM_InitBlock( void )
{
Mem_Set( r_lmState.allocated, 0, sizeof( r_lmState.allocated ));
}
static int LM_AllocBlock( int w, int h, int *x, int *y )
{
int i, j, best, best2, texnum;
for( texnum = 0; texnum < MAX_LIGHTMAPS; texnum++ )
{
best = BLOCK_HEIGHT;
for( i = 0; i < BLOCK_WIDTH - w; i++ )
{
best2 = 0;
for( j = 0; j < w; j++ )
{
if( r_lmState.allocated[texnum][i+j] >= best )
break;
if( r_lmState.allocated[texnum][i+j] > best2 )
best2 = r_lmState.allocated[texnum][i+j];
}
if( j == w )
{
// this is a valid spot
*x = i;
*y = best = best2;
}
}
if( best + h > BLOCK_HEIGHT )
continue;
for( i = 0; i < w; i++ )
r_lmState.allocated[texnum][*x+i] = best + h;
return texnum;
}
Host_Error( "AllocBlock: full\n" );
return 0;
}
/*
===============
R_BuildLightMap
Combine and scale multiple lightmaps into the 8.8 format in blocklights
===============
*/
static void R_BuildLightMap( msurface_t *surf, byte *dest, int stride )
{
int smax, tmax, t, s, i;
int size, map, blocksize;
uint scale;
uint *bl;
color24 *lm;
surf->cached_dlight = (r_numdlights > 0) ? true : false;
smax = (surf->extents[0] >> 4) + 1;
tmax = (surf->extents[1] >> 4) + 1;
size = smax * tmax;
blocksize = size * 3;
lm = surf->samples;
Mem_Set( r_blockLights, 0, sizeof( uint ) * blocksize );
// add all the lightmaps
for( map = 0; map < MAXLIGHTMAPS && surf->styles[map] != 255 && lm; map++ )
{
scale = RI.lightstylevalue[surf->styles[map]];
surf->cached_light[map] = scale;
for( i = 0, bl = r_blockLights; i < size; i++, bl += 3, lm++ )
{
bl[0] += lm->r * scale;
bl[1] += lm->g * scale;
bl[2] += lm->b * scale;
}
}
// add all the dynamic lights
if( r_numdlights )
R_AddDynamicLights( surf );
// put into texture format
stride -= (smax << 2);
bl = r_blockLights;
for( t = 0; t < tmax; t++, dest += stride )
{
for( s = 0; s < smax; s++ )
{
dest[0] = min((bl[0] >> 7), 255 );
dest[1] = min((bl[1] >> 7), 255 );
dest[2] = min((bl[2] >> 7), 255 );
dest[3] = 255;
bl += 3;
dest += 4;
}
}
}
static void LM_UploadBlock( int lightmapnum )
{
wrect_t *rect;
r_lmState.lightmap_modified[lightmapnum] = false;
rect = &r_lmState.lightmap_rectchange[lightmapnum];
pglTexSubImage2D( GL_TEXTURE_2D, 0, 0, rect->right, BLOCK_WIDTH, rect->bottom, GL_RGBA, GL_UNSIGNED_BYTE,
&r_lmState.lightmaps[(lightmapnum * BLOCK_HEIGHT + rect->right) * BLOCK_WIDTH * 4] );
// reset rectangle
rect->left = BLOCK_WIDTH;
rect->right = BLOCK_HEIGHT;
rect->top = 0;
rect->bottom = 0;
}
/*
========================
GL_CreateSurfaceLightmap
========================
*/
void GL_CreateSurfaceLightmap( msurface_t *surf )
{
int smax, tmax;
byte *base;
texture_t *tex;
if( !cl.worldmodel->lightdata ) return;
if( surf->flags & SURF_DRAWTILED )
return;
smax = ( surf->extents[0] >> 4 ) + 1;
tmax = ( surf->extents[1] >> 4 ) + 1;
tex = surf->texinfo->texture;
if( smax > BLOCK_WIDTH )
Host_Error( "GL_CreateLightmap: lightmap width %d > %d, %s\n", smax, BLOCK_WIDTH, tex->name );
if( tmax > BLOCK_HEIGHT )
Host_Error( "GL_CreateLightmap: lightmap height %d > %d, %s\n", tmax, BLOCK_HEIGHT, tex->name );
if( smax * tmax > BLOCK_WIDTH * BLOCK_HEIGHT )
Host_Error( "GL_CreateLightmap: lightmap size too big\n" );
surf->lightmaptexturenum = LM_AllocBlock( smax, tmax, &surf->light_s, &surf->light_t );
base = r_lmState.lightmaps + surf->lightmaptexturenum * BLOCK_WIDTH * BLOCK_HEIGHT * 4;
base += (surf->light_t * BLOCK_WIDTH + surf->light_s) * 4;
r_numdlights = 0;
R_BuildLightMap( surf, base, BLOCK_WIDTH * 4 );
}
/*
==================
GL_BuildLightmaps
Builds the lightmap texture
with all the surfaces from all brush models
==================
*/
void GL_BuildLightmaps( void )
{
int i, j;
rgbdata_t r_lightmap;
char lmName[16];
model_t *m;
// release old lightmaps
for( i = 0; i < MAX_LIGHTMAPS; i++ )
{
if( !tr.lightmapTextures[i] ) break;
GL_FreeTexture( tr.lightmapTextures[i] );
}
Mem_Set( tr.lightmapTextures, 0, sizeof( tr.lightmapTextures ));
Mem_Set( visbytes, 0x00, sizeof( visbytes ));
Mem_Set( &r_lmState, 0, sizeof( r_lmState ));
skychain = waterchain = NULL;
// setup all the lightstyles
R_AnimateLight();
LM_InitBlock();
for( i = 1; i < MAX_MODELS; i++ )
{
if(( m = CM_ClipHandleToModel( i )) == NULL )
continue;
if( m->name[0] == '*' || m->type != mod_brush )
continue;
loadmodel = m;
for( j = 0; j < m->numsurfaces; j++ )
{
GL_CreateSurfaceLightmap( m->surfaces + j );
if( m->surfaces[i].flags & SURF_DRAWTURB )
continue;
if( m->surfaces[i].flags & SURF_DRAWSKY && world.version == Q1BSP_VERSION )
continue;
GL_BuildPolygonFromSurface( m->surfaces + j );
}
}
loadmodel = NULL;
// upload all lightmaps that were filled
for( i = 0; i < MAX_LIGHTMAPS; i++ )
{
if( !r_lmState.allocated[i][0] ) break; // no more used
r_lmState.lightmap_modified[i] = false;
r_lmState.lightmap_rectchange[i].left = BLOCK_WIDTH;
r_lmState.lightmap_rectchange[i].right = BLOCK_HEIGHT;
r_lmState.lightmap_rectchange[i].top = 0;
r_lmState.lightmap_rectchange[i].bottom = 0;
Mem_Set( &r_lightmap, 0, sizeof( r_lightmap ));
com.snprintf( lmName, sizeof( lmName ), "*lightmap%i", i );
r_lightmap.width = BLOCK_WIDTH;
r_lightmap.height = BLOCK_HEIGHT;
r_lightmap.type = PF_RGBA_32;
r_lightmap.size = r_lightmap.width * r_lightmap.height * 4;
r_lightmap.flags = IMAGE_HAS_COLOR; // FIXME: detecting grayscale lightmaps for quake1
r_lightmap.buffer = (byte *)&r_lmState.lightmaps[r_lightmap.size*i];
tr.lightmapTextures[i] = GL_LoadTextureInternal( lmName, &r_lightmap, TF_FONT|TF_LIGHTMAP, false );
GL_SetTextureType( tr.lightmapTextures[i], TEX_LIGHTMAP );
}
}