/* gl_rsurf.c - surface-related refresh code Copyright (C) 2010 Uncle Mike This program 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 3 of the License, or (at your option) any later version. This program 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. */ #include "common.h" #include "client.h" #include "gl_local.h" #include "mod_local.h" #include "mathlib.h" typedef struct { int allocated[BLOCK_WIDTH]; int current_lightmap_texture; msurface_t *dynamic_surfaces; msurface_t *lightmap_surfaces[MAX_LIGHTMAPS]; byte lightmap_buffer[BLOCK_WIDTH*BLOCK_HEIGHT*4]; } gllightmapstate_t; static vec2_t world_orthocenter; static vec2_t world_orthohalf; static byte visbytes[MAX_MAP_LEAFS/8]; static uint r_blocklights[BLOCK_WIDTH*BLOCK_HEIGHT*3]; static glpoly_t *fullbright_polys[MAX_TEXTURES]; static qboolean draw_fullbrights = false; static glpoly_t *detail_polys[MAX_TEXTURES]; static qboolean draw_details = false; static gllightmapstate_t gl_lms; static msurface_t *skychain = NULL; static void LM_UploadBlock( int lightmapnum ); byte *Mod_GetCurrentVis( void ) { return Mod_LeafPVS( r_viewleaf, cl.worldmodel ); } void Mod_SetOrthoBounds( float *mins, float *maxs ) { if( clgame.drawFuncs.GL_OrthoBounds ) { clgame.drawFuncs.GL_OrthoBounds( mins, maxs ); } Vector2Average( maxs, mins, world_orthocenter ); Vector2Subtract( maxs, world_orthocenter, world_orthohalf ); } 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, vertsDiv; vec3_t front[SUBDIVIDE_SIZE], back[SUBDIVIDE_SIZE], total; float dist[SUBDIVIDE_SIZE], total_s, total_t, total_ls, total_lt; 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 = total_ls = 0.0f; total_t = total_lt = 0.0f; for( i = 0; i < numverts; i++, verts += 3 ) { VectorCopy( verts, poly->verts[i+1] ); VectorAdd( total, verts, total ); if( warpface->flags & SURF_DRAWTURB ) { s = DotProduct( verts, warpface->texinfo->vecs[0] ); t = DotProduct( verts, warpface->texinfo->vecs[1] ); } else { s = DotProduct( verts, warpface->texinfo->vecs[0] ) + warpface->texinfo->vecs[0][3]; t = DotProduct( verts, warpface->texinfo->vecs[1] ) + warpface->texinfo->vecs[1][3]; s /= warpface->texinfo->texture->width; t /= warpface->texinfo->texture->height; } poly->verts[i+1][3] = s; poly->verts[i+1][4] = t; total_s += s; total_t += t; // for speed reasons if( !( warpface->flags & SURF_DRAWTURB )) { // lightmap texture coordinates s = DotProduct( verts, warpface->texinfo->vecs[0] ) + warpface->texinfo->vecs[0][3]; s -= warpface->texturemins[0]; s += warpface->light_s * LM_SAMPLE_SIZE; s += 8; s /= BLOCK_WIDTH * LM_SAMPLE_SIZE; //fa->texinfo->texture->width; t = DotProduct( verts, warpface->texinfo->vecs[1] ) + warpface->texinfo->vecs[1][3]; t -= warpface->texturemins[1]; t += warpface->light_t * LM_SAMPLE_SIZE; t += 8; t /= BLOCK_HEIGHT * LM_SAMPLE_SIZE; //fa->texinfo->texture->height; poly->verts[i+1][5] = s; poly->verts[i+1][6] = t; total_ls += s; total_lt += t; } } vertsDiv = ( 1.0f / (float)numverts ); VectorScale( total, vertsDiv, poly->verts[0] ); poly->verts[0][3] = total_s * vertsDiv; poly->verts[0][4] = total_t * vertsDiv; if( !( warpface->flags & SURF_DRAWTURB )) { poly->verts[0][5] = total_ls * vertsDiv; poly->verts[0][6] = total_lt * vertsDiv; } // copy first vertex to last Q_memcpy( 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( model_t *mod, 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( !mod || fa->polys ) return; if( !fa->texinfo || !fa->texinfo->texture ) return; // bad polygon ? // reconstruct the polygon pedges = mod->edges; lnumverts = fa->numedges; vertpage = 0; // draw texture poly = Mem_Alloc( mod->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 = mod->surfedges[fa->firstedge + i]; if( lindex > 0 ) { r_pedge = &pedges[lindex]; vec = mod->vertexes[r_pedge->v[0]].position; } else { r_pedge = &pedges[-lindex]; vec = mod->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 * LM_SAMPLE_SIZE; s += 8; s /= BLOCK_WIDTH * LM_SAMPLE_SIZE; //fa->texinfo->texture->width; t = DotProduct( vec, fa->texinfo->vecs[1] ) + fa->texinfo->vecs[1][3]; t -= fa->texturemins[1]; t += fa->light_t * LM_SAMPLE_SIZE; t += 8; t /= BLOCK_HEIGHT * LM_SAMPLE_SIZE; //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 surfacenum ) { int reletive; int count, speed; // random tiling textures if( base->anim_total < 0 ) { reletive = abs( surfacenum ) % abs( base->anim_total ); count = 0; while( base->anim_min > reletive || base->anim_max <= reletive ) { base = base->anim_next; if( !base ) Host_Error( "R_TextureRandomTiling: broken loop\n" ); if( ++count > 100 ) Host_Error( "R_TextureRandomTiling: infinite loop\n" ); } return base; } 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.sky_sphere || 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; } /* =============== R_AddDynamicLights =============== */ void R_AddDynamicLights( msurface_t *surf ) { float dist, rad, minlight; int lnum, s, t, sd, td, smax, tmax; float sl, tl, sacc, tacc; vec3_t impact, origin_l; mtexinfo_t *tex; dlight_t *dl; uint *bl; // no dlighted surfaces here if( !R_CountSurfaceDlights( surf )) return; 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 & BIT( lnum ))) continue; // not lit by this light dl = &cl_dlights[lnum]; // transform light origin to local bmodel space if( !tr.modelviewIdentity ) Matrix4x4_VectorITransform( RI.objectMatrix, dl->origin, origin_l ); else VectorCopy( dl->origin, origin_l ); rad = dl->radius; dist = PlaneDiff( origin_l, surf->plane ); rad -= fabs( dist ); // rad is now the highest intensity on the plane minlight = dl->minlight; if( rad < minlight ) continue; minlight = rad - minlight; if( surf->plane->type < 3 ) { VectorCopy( origin_l, impact ); impact[surf->plane->type] -= dist; } else VectorMA( origin_l, -dist, surf->plane->normal, impact ); sl = DotProduct( impact, tex->vecs[0] ) + tex->vecs[0][3] - surf->texturemins[0]; tl = DotProduct( impact, tex->vecs[1] ) + tex->vecs[1][3] - surf->texturemins[1]; bl = r_blocklights; for( t = 0, tacc = 0; t < tmax; t++, tacc += LM_SAMPLE_SIZE ) { td = tl - tacc; if( td < 0 ) td = -td; for( s = 0, sacc = 0; s < smax; s++, sacc += LM_SAMPLE_SIZE, bl += 3 ) { sd = sl - sacc; if( sd < 0 ) sd = -sd; if( sd > td ) dist = sd + (td >> 1); else dist = td + (sd >> 1); if( dist < minlight ) { bl[0] += ( rad - dist ) * TextureToTexGamma( dl->color.r ); bl[1] += ( rad - dist ) * TextureToTexGamma( dl->color.g ); bl[2] += ( rad - dist ) * TextureToTexGamma( dl->color.b ); } } } } } /* ================ R_SetCacheState ================ */ void R_SetCacheState( msurface_t *surf ) { int maps; for( maps = 0; maps < MAXLIGHTMAPS && surf->styles[maps] != 255; maps++ ) { surf->cached_light[maps] = RI.lightstylevalue[surf->styles[maps]]; } } /* ============================================================================= LIGHTMAP ALLOCATION ============================================================================= */ static void LM_InitBlock( void ) { Q_memset( gl_lms.allocated, 0, sizeof( gl_lms.allocated )); } static int LM_AllocBlock( int w, int h, int *x, int *y ) { int i, j; int best, best2; best = BLOCK_HEIGHT; for( i = 0; i < BLOCK_WIDTH - w; i++ ) { best2 = 0; for( j = 0; j < w; j++ ) { if( gl_lms.allocated[i+j] >= best ) break; if( gl_lms.allocated[i+j] > best2 ) best2 = gl_lms.allocated[i+j]; } if( j == w ) { // this is a valid spot *x = i; *y = best = best2; } } if( best + h > BLOCK_HEIGHT ) return false; for( i = 0; i < w; i++ ) gl_lms.allocated[*x + i] = best + h; return true; } static void LM_UploadBlock( qboolean dynamic ) { int i; if( dynamic ) { int height = 0; for( i = 0; i < BLOCK_WIDTH; i++ ) { if( gl_lms.allocated[i] > height ) height = gl_lms.allocated[i]; } GL_MBind( tr.dlightTexture ); pglTexSubImage2D( GL_TEXTURE_2D, 0, 0, 0, BLOCK_WIDTH, height, GL_RGBA, GL_UNSIGNED_BYTE, gl_lms.lightmap_buffer ); } else { rgbdata_t r_lightmap; char lmName[16]; i = gl_lms.current_lightmap_texture; // upload static lightmaps only during loading Q_memset( &r_lightmap, 0, sizeof( r_lightmap )); Q_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 = ( world.version == Q1BSP_VERSION ) ? 0 : IMAGE_HAS_COLOR; r_lightmap.buffer = gl_lms.lightmap_buffer; tr.lightmapTextures[i] = GL_LoadTextureInternal( lmName, &r_lightmap, TF_FONT, false ); GL_SetTextureType( tr.lightmapTextures[i], TEX_LIGHTMAP ); if( ++gl_lms.current_lightmap_texture == MAX_LIGHTMAPS ) Host_Error( "AllocBlock: full\n" ); } } /* ================= R_BuildLightmap Combine and scale multiple lightmaps into the floating format in r_blocklights ================= */ static void R_BuildLightMap( msurface_t *surf, byte *dest, int stride ) { int smax, tmax; uint *bl, scale; int i, map, size, s, t; color24 *lm; smax = ( surf->extents[0] >> 4 ) + 1; tmax = ( surf->extents[1] >> 4 ) + 1; size = smax * tmax; lm = surf->samples; Q_memset( r_blocklights, 0, sizeof( uint ) * size * 3 ); // add all the lightmaps for( map = 0; map < MAXLIGHTMAPS && surf->styles[map] != 255 && lm; map++ ) { scale = RI.lightstylevalue[surf->styles[map]]; for( i = 0, bl = r_blocklights; i < size; i++, bl += 3, lm++ ) { bl[0] += TextureToTexGamma( lm->r ) * scale; bl[1] += TextureToTexGamma( lm->g ) * scale; bl[2] += TextureToTexGamma( lm->b ) * scale; } } // add all the dynamic lights if( surf->dlightframe == tr.framecount ) 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; } } } /* ================ DrawGLPoly ================ */ void DrawGLPoly( glpoly_t *p, float xScale, float yScale ) { float *v; float sOffset, sy; float tOffset, cy; cl_entity_t *e = RI.currententity; int i, hasScale = false; 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; SinCos( flAngle * ( M_PI / 180.0f ), &sy, &cy ); sOffset = RI.refdef.time * cy * flRate; tOffset = RI.refdef.time * sy * 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; } if( xScale != 0.0f && yScale != 0.0f ) hasScale = true; pglBegin( GL_POLYGON ); for( i = 0, v = p->verts[0]; i < p->numverts; i++, v += VERTEXSIZE ) { if( hasScale ) pglTexCoord2f(( v[3] + sOffset ) * xScale, ( v[4] + tOffset ) * yScale ); else pglTexCoord2f( v[3] + sOffset, v[4] + tOffset ); pglVertex3fv( v ); } pglEnd(); } /* ================ DrawGLPolyChain Render lightmaps ================ */ void DrawGLPolyChain( glpoly_t *p, float soffset, float toffset ) { qboolean dynamic = true; if( soffset == 0.0f && toffset == 0.0f ) dynamic = false; for( ; p != NULL; p = p->chain ) { float *v; int i; pglBegin( GL_POLYGON ); v = p->verts[0]; for( i = 0; i < p->numverts; i++, v += VERTEXSIZE ) { if( !dynamic ) pglTexCoord2f( v[5], v[6] ); else pglTexCoord2f( v[5] - soffset, v[6] - toffset ); pglVertex3fv( v ); } pglEnd (); } } /* ================ R_BlendLightmaps ================ */ void R_BlendLightmaps( void ) { msurface_t *surf, *newsurf = NULL; mextrasurf_t *info; int i; if( r_fullbright->integer || !cl.worldmodel->lightdata ) return; if( RI.currententity ) { // check for rendermode switch( RI.currententity->curstate.rendermode ) { case kRenderTransTexture: if( r_lighting_extended->integer == 2 ) break; case kRenderTransColor: case kRenderTransAdd: case kRenderGlow: return; // no lightmaps } if( RI.currententity->curstate.effects & EF_FULLBRIGHT ) return; // disabled by user } if( !r_lightmap->integer ) { pglEnable( GL_BLEND ); if( !glState.drawTrans ) { // lightmapped solid surfaces pglDepthMask( GL_FALSE ); pglDepthFunc( GL_EQUAL ); } pglDisable( GL_ALPHA_TEST ); pglBlendFunc( GL_ZERO, GL_SRC_COLOR ); pglTexEnvi( GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE ); } // render static lightmaps first for( i = 0; i < MAX_LIGHTMAPS; i++ ) { if( gl_lms.lightmap_surfaces[i] ) { GL_MBind( tr.lightmapTextures[i] ); for( surf = gl_lms.lightmap_surfaces[i]; surf != NULL; surf = surf->lightmapchain ) { if( surf->polys ) DrawGLPolyChain( surf->polys, 0.0f, 0.0f ); } } } // render dynamic lightmaps if( r_dynamic->integer ) { LM_InitBlock(); GL_MBind( tr.dlightTexture ); newsurf = gl_lms.dynamic_surfaces; for( surf = gl_lms.dynamic_surfaces; surf != NULL; surf = surf->lightmapchain ) { int smax, tmax; byte *base; smax = ( surf->extents[0] >> 4 ) + 1; tmax = ( surf->extents[1] >> 4 ) + 1; info = SURF_INFO( surf, RI.currentmodel ); if( LM_AllocBlock( smax, tmax, &info->dlight_s, &info->dlight_t )) { base = gl_lms.lightmap_buffer; base += ( info->dlight_t * BLOCK_WIDTH + info->dlight_s ) * 4; R_BuildLightMap( surf, base, BLOCK_WIDTH * 4 ); } else { msurface_t *drawsurf; // upload what we have so far LM_UploadBlock( true ); // draw all surfaces that use this lightmap for( drawsurf = newsurf; drawsurf != surf; drawsurf = drawsurf->lightmapchain ) { if( drawsurf->polys ) { info = SURF_INFO( drawsurf, RI.currentmodel ); DrawGLPolyChain( drawsurf->polys, ( drawsurf->light_s - info->dlight_s ) * ( 1.0f / 128.0f ), ( drawsurf->light_t - info->dlight_t ) * ( 1.0f / 128.0f )); } } newsurf = drawsurf; // clear the block LM_InitBlock(); info = SURF_INFO( surf, RI.currentmodel ); // try uploading the block now if( !LM_AllocBlock( smax, tmax, &info->dlight_s, &info->dlight_t )) Host_Error( "AllocBlock: full\n" ); base = gl_lms.lightmap_buffer; base += ( info->dlight_t * BLOCK_WIDTH + info->dlight_s ) * 4; R_BuildLightMap( surf, base, BLOCK_WIDTH * 4 ); } } // draw remainder of dynamic lightmaps that haven't been uploaded yet if( newsurf ) LM_UploadBlock( true ); for( surf = newsurf; surf != NULL; surf = surf->lightmapchain ) { if( surf->polys ) { info = SURF_INFO( surf, RI.currentmodel ); DrawGLPolyChain( surf->polys, ( surf->light_s - info->dlight_s ) * ( 1.0f / 128.0f ), ( surf->light_t - info->dlight_t ) * ( 1.0f / 128.0f )); } } } if( !r_lightmap->integer ) { pglDisable( GL_BLEND ); if( !glState.drawTrans ) { // restore depth state pglDepthMask( GL_TRUE ); pglDepthFunc( GL_LEQUAL ); } pglDisable( GL_ALPHA_TEST ); pglTexEnvi( GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE ); } } /* ================ R_RenderFullbrights ================ */ void R_RenderFullbrights( void ) { glpoly_t *p; int i; if( !draw_fullbrights ) return; if( !RI.fogCustom ) pglDisable( GL_FOG ); pglEnable( GL_BLEND ); pglDepthMask( GL_FALSE ); pglDisable( GL_ALPHA_TEST ); pglBlendFunc( GL_ONE, GL_ONE ); pglTexEnvi( GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE ); for( i = 1; i < MAX_TEXTURES; i++ ) { if( !fullbright_polys[i] ) continue; GL_Bind( GL_TEXTURE0, i ); for( p = fullbright_polys[i]; p; p = p->next ) { if( p->flags & SURF_DRAWTURB ) EmitWaterPolys( p, ( p->flags & SURF_NOCULL )); else DrawGLPoly( p, 0.0f, 0.0f ); } fullbright_polys[i] = NULL; } pglDisable( GL_BLEND ); pglDepthMask( GL_TRUE ); pglDisable( GL_ALPHA_TEST ); pglTexEnvi( GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE ); draw_fullbrights = false; // restore for here if( RI.fogEnabled && !RI.refdef.onlyClientDraw ) pglEnable( GL_FOG ); } /* ================ R_RenderDetails ================ */ void R_RenderDetails( void ) { gltexture_t *glt; glpoly_t *p; int i; if( !draw_details ) return; pglEnable( GL_BLEND ); pglBlendFunc( GL_DST_COLOR, GL_SRC_COLOR ); pglTexEnvi( GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_DECAL ); if( RI.currententity->curstate.rendermode == kRenderTransAlpha ) pglDepthFunc( GL_EQUAL ); for( i = 1; i < MAX_TEXTURES; i++ ) { if( !detail_polys[i] ) continue; GL_Bind( GL_TEXTURE0, i ); glt = R_GetTexture( i ); for( p = detail_polys[i]; p; p = p->next ) DrawGLPoly( p, glt->xscale, glt->yscale ); detail_polys[i] = NULL; } pglDisable( GL_BLEND ); pglTexEnvi( GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE ); if( RI.currententity->curstate.rendermode == kRenderTransAlpha ) pglDepthFunc( GL_LEQUAL ); draw_details = false; } /* ================ R_RenderBrushPoly ================ */ void R_RenderBrushPoly( msurface_t *fa ) { texture_t *t; int maps; qboolean is_dynamic = false; qboolean is_mirror = false; if( RI.currententity == clgame.entities ) r_stats.c_world_polys++; else r_stats.c_brush_polys++; if( fa->flags & SURF_DRAWSKY ) { if( world.sky_sphere ) { // warp texture, no lightmaps EmitSkyLayers( fa ); } return; } t = R_TextureAnimation( fa->texinfo->texture, fa - RI.currententity->model->surfaces ); if( RP_NORMALPASS() && fa->flags & SURF_REFLECT ) { if( SURF_INFO( fa, RI.currentmodel )->mirrortexturenum ) { GL_MBind( SURF_INFO( fa, RI.currentmodel )->mirrortexturenum ); is_mirror = true; } else GL_MBind( t->gl_texturenum ); // dummy // DEBUG: reset the mirror texture after drawing SURF_INFO( fa, RI.currentmodel )->mirrortexturenum = 0; } else GL_MBind( t->gl_texturenum ); if( fa->flags & SURF_DRAWTURB ) { // warp texture, no lightmaps EmitWaterPolys( fa->polys, ( fa->flags & SURF_NOCULL )); return; } if( t->fb_texturenum ) { // HACKHACK: store fullbrights in poly->next (only for non-water surfaces) fa->polys->next = fullbright_polys[t->fb_texturenum]; fullbright_polys[t->fb_texturenum] = fa->polys; draw_fullbrights = true; } else if( r_detailtextures->integer && t->dt_texturenum ) { // HACKHACK: store details in poly->next (only for non-water surfaces) // can't rendering both luma and detail textures simultaneously fa->polys->next = detail_polys[t->dt_texturenum]; detail_polys[t->dt_texturenum] = fa->polys; draw_details = true; } if( is_mirror ) R_BeginDrawMirror( fa ); DrawGLPoly( fa->polys, 0.0f, 0.0f ); if( is_mirror ) R_EndDrawMirror(); DrawSurfaceDecals( fa ); // NOTE: draw mirror through in mirror show dummy lightmapped texture if( fa->flags & SURF_REFLECT && RP_NORMALPASS() && r_lighting_extended->integer < 2 ) return; // no lightmaps for mirror if( fa->flags & SURF_DRAWTILED ) return; // no lightmaps anyway // check for lightmap modification for( maps = 0; maps < MAXLIGHTMAPS && fa->styles[maps] != 255; maps++ ) { if( RI.lightstylevalue[fa->styles[maps]] != fa->cached_light[maps] ) goto dynamic; } // dynamic this frame or dynamic previously if(( fa->dlightframe == tr.framecount )) { dynamic: // NOTE: at this point we have only valid textures if( r_dynamic->integer ) is_dynamic = true; } if( is_dynamic ) { if(( fa->styles[maps] >= 32 || fa->styles[maps] == 0 ) && ( fa->dlightframe != tr.framecount )) { byte temp[34*34*4]; int smax, tmax; smax = ( fa->extents[0] >> 4 ) + 1; tmax = ( fa->extents[1] >> 4 ) + 1; R_BuildLightMap( fa, temp, smax * 4 ); R_SetCacheState( fa ); GL_MBind( tr.lightmapTextures[fa->lightmaptexturenum] ); pglTexSubImage2D( GL_TEXTURE_2D, 0, fa->light_s, fa->light_t, smax, tmax, GL_RGBA, GL_UNSIGNED_BYTE, temp ); fa->lightmapchain = gl_lms.lightmap_surfaces[fa->lightmaptexturenum]; gl_lms.lightmap_surfaces[fa->lightmaptexturenum] = fa; } else { fa->lightmapchain = gl_lms.dynamic_surfaces; gl_lms.dynamic_surfaces = fa; } } else { fa->lightmapchain = gl_lms.lightmap_surfaces[fa->lightmaptexturenum]; gl_lms.lightmap_surfaces[fa->lightmaptexturenum] = fa; } } /* ================ 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 ); R_LoadIdentity(); // set identity matrix // restore worldmodel RI.currententity = clgame.entities; RI.currentmodel = RI.currententity->model; // 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.sky_sphere ) R_DrawSkyChain( s ); } else { if(( s->flags & SURF_DRAWTURB ) && RI.refdef.movevars->wateralpha < 1.0f ) continue; // draw translucent water later 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; if( !RI.drawWorld || RI.refdef.onlyClientDraw ) return; // non-transparent water is already drawed if( RI.refdef.movevars->wateralpha >= 1.0f ) return; // go back to the world matrix R_LoadIdentity(); pglEnable( GL_BLEND ); pglDepthMask( GL_FALSE ); pglDisable( GL_ALPHA_TEST ); pglBlendFunc( GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA ); pglTexEnvi( GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE ); pglColor4f( 1.0f, 1.0f, 1.0f, RI.refdef.movevars->wateralpha ); 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, ( s->flags & SURF_NOCULL )); t->texturechain = NULL; } pglDisable( GL_BLEND ); pglDepthMask( GL_TRUE ); pglDisable( GL_ALPHA_TEST ); pglTexEnvi( GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE ); pglColor4ub( 255, 255, 255, 255 ); } /* ================= 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.vieworg, org1, vecLength ); len1 = VectorLength( vecLength ); VectorSubtract( RI.vieworg, org2, vecLength ); len2 = VectorLength( vecLength ); if( len1 > len2 ) return -1; if( len1 < len2 ) return 1; return 0; } /* ================= R_DrawBrushModel ================= */ void R_DrawBrushModel( cl_entity_t *e ) { int i, k, num_sorted; qboolean need_sort = false; vec3_t origin_l, oldorigin; 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; Q_memset( gl_lms.lightmap_surfaces, 0, sizeof( gl_lms.lightmap_surfaces )); gl_lms.dynamic_surfaces = NULL; if( rotated ) R_RotateForEntity( e ); else R_TranslateForEntity( e ); e->visframe = tr.framecount; // visible if( rotated ) Matrix4x4_VectorITransform( RI.objectMatrix, RI.cullorigin, tr.modelorg ); else VectorSubtract( RI.cullorigin, e->origin, tr.modelorg ); // calculate dynamic lighting for bmodel for( k = 0, l = cl_dlights; k < MAX_DLIGHTS; k++, l++ ) { if( l->die < cl.time || !l->radius ) continue; VectorCopy( l->origin, oldorigin ); // save lightorigin Matrix4x4_VectorITransform( RI.objectMatrix, l->origin, origin_l ); VectorCopy( origin_l, l->origin ); // move light in bmodel space R_MarkLights( l, 1<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: need_sort = true; 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; case kRenderTransAlpha: // NOTE: brushes can't change renderamt for 'Solid' mode pglAlphaFunc( GL_GEQUAL, 0.5f ); 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_RenderFullbrights(); R_RenderDetails(); 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 for( k = 0, l = cl_dlights; k < MAX_DLIGHTS; k++, l++ ) { if( l->die < cl.time || !l->radius ) continue; R_MarkLights( l, 1<nodes + clmodel->hulls[0].firstclipnode ); } psurf = &clmodel->surfaces[clmodel->firstmodelsurface]; for( i = 0; i < clmodel->nummodelsurfaces; i++, psurf++ ) { if( R_CullSurface( psurf, RI.clipFlags )) continue; if( psurf->flags & SURF_DRAWSKY && !world.sky_sphere ) { // make sky chain to right clip the skybox psurf->texturechain = skychain; skychain = psurf; } else { 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++ ) { 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<minmaxs, node->minmaxs + 3, clipplane ); if( clipped == 2 ) return; if( clipped == 1 ) clipflags &= ~(1<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( tr.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.sky_sphere ) { // 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_CullNodeTopView cull node by user rectangle (simple scissor) ================ */ qboolean R_CullNodeTopView( mnode_t *node ) { vec2_t delta, size; vec3_t center, half; // build the node center and half-diagonal VectorAverage( node->minmaxs, node->minmaxs + 3, center ); VectorSubtract( node->minmaxs + 3, center, half ); // cull against the screen frustum or the appropriate area's frustum. Vector2Subtract( center, world_orthocenter, delta ); Vector2Add( half, world_orthohalf, size ); return ( fabs( delta[0] ) > size[0] ) || ( fabs( delta[1] ) > size[1] ); } /* ================ R_DrawTopViewLeaf ================ */ static void R_DrawTopViewLeaf( mleaf_t *pleaf, uint clipflags ) { msurface_t **mark, *surf; int i; for( i = 0, mark = pleaf->firstmarksurface; i < pleaf->nummarksurfaces; i++, mark++ ) { surf = *mark; // don't process the same surface twice if( surf->visframe == tr.framecount ) continue; surf->visframe = tr.framecount; if( R_CullSurface( surf, clipflags )) continue; if(!( surf->flags & SURF_DRAWSKY )) { surf->texturechain = surf->texinfo->texture->texturechain; surf->texinfo->texture->texturechain = surf; } } // deal with model fragments in this leaf if( pleaf->efrags ) R_StoreEfrags( &pleaf->efrags ); r_stats.c_world_leafs++; } /* ================ R_DrawWorldTopView ================ */ void R_DrawWorldTopView( mnode_t *node, uint clipflags ) { const mplane_t *clipplane; int c, clipped; msurface_t *surf; do { if( node->contents == CONTENTS_SOLID ) return; // hit a solid leaf if( node->visframe != tr.visframecount ) return; if( clipflags ) { for( c = 0, clipplane = RI.frustum; c < 6; c++, clipplane++ ) { if(!( clipflags & ( 1<minmaxs, node->minmaxs + 3, clipplane ); if( clipped == 2 ) return; if( clipped == 1 ) clipflags &= ~(1<contents < 0 ) { R_DrawTopViewLeaf( (mleaf_t *)node, clipflags ); return; } // draw stuff for( c = node->numsurfaces, surf = cl.worldmodel->surfaces + node->firstsurface; c; c--, surf++ ) { // don't process the same surface twice if( surf->visframe == tr.framecount ) continue; surf->visframe = tr.framecount; if( R_CullSurface( surf, clipflags )) continue; if(!( surf->flags & SURF_DRAWSKY )) { surf->texturechain = surf->texinfo->texture->texturechain; surf->texinfo->texture->texturechain = surf; } } // recurse down both children, we don't care the order... R_DrawWorldTopView( node->children[0], clipflags ); node = node->children[1]; } while( node ); } /* ============= R_DrawTriangleOutlines ============= */ void R_DrawTriangleOutlines( void ) { int i, j; msurface_t *surf; glpoly_t *p; float *v; if( !gl_wireframe->integer ) return; pglDisable( GL_TEXTURE_2D ); pglDisable( GL_DEPTH_TEST ); pglColor4f( 1.0f, 1.0f, 1.0f, 1.0f ); pglPolygonMode( GL_FRONT_AND_BACK, GL_LINE ); // render static surfaces first for( i = 0; i < MAX_LIGHTMAPS; i++ ) { for( surf = gl_lms.lightmap_surfaces[i]; surf != NULL; surf = surf->lightmapchain ) { p = surf->polys; for( ; p != NULL; p = p->chain ) { pglBegin( GL_POLYGON ); v = p->verts[0]; for( j = 0; j < p->numverts; j++, v += VERTEXSIZE ) pglVertex3fv( v ); pglEnd (); } } } // render surfaces with dynamic lightmaps for( surf = gl_lms.dynamic_surfaces; surf != NULL; surf = surf->lightmapchain ) { p = surf->polys; for( ; p != NULL; p = p->chain ) { pglBegin( GL_POLYGON ); v = p->verts[0]; for( j = 0; j < p->numverts; j++, v += VERTEXSIZE ) pglVertex3fv( v ); pglEnd (); } } pglPolygonMode( GL_FRONT_AND_BACK, GL_FILL ); pglEnable( GL_DEPTH_TEST ); pglEnable( GL_TEXTURE_2D ); } /* ============= R_DrawWorld ============= */ void R_DrawWorld( void ) { // paranoia issues: when gl_renderer is "0" we need have something valid for currententity // to prevent crashing until HeadShield drawing. RI.currententity = clgame.entities; RI.currentmodel = RI.currententity->model; if( !RI.drawWorld || RI.refdef.onlyClientDraw ) return; VectorCopy( RI.cullorigin, tr.modelorg ); Q_memset( gl_lms.lightmap_surfaces, 0, sizeof( gl_lms.lightmap_surfaces )); Q_memset( fullbright_polys, 0, sizeof( fullbright_polys )); Q_memset( detail_polys, 0, sizeof( detail_polys )); RI.currentWaveHeight = RI.waveHeight; GL_SetRenderMode( kRenderNormal ); gl_lms.dynamic_surfaces = NULL; R_ClearSkyBox (); // draw the world fog R_DrawFog (); if( RI.drawOrtho ) { R_DrawWorldTopView( cl.worldmodel->nodes, RI.clipFlags ); } else { R_RecursiveWorldNode( cl.worldmodel->nodes, RI.clipFlags ); } R_DrawStaticBrushes(); R_DrawTextureChains(); R_BlendLightmaps(); R_RenderFullbrights(); R_RenderDetails(); if( skychain ) R_DrawSkyBox(); skychain = NULL; R_DrawTriangleOutlines (); } /* =============== 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_novis->modified ) { // force recalc viewleaf r_novis->modified = false; r_viewleaf = NULL; } 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 || RI.drawOrtho || !r_viewleaf || !cl.worldmodel->visdata ) { // mark everything for( i = 0; i < cl.worldmodel->numleafs; i++ ) cl.worldmodel->leafs[i+1].visframe = tr.visframecount; for( i = 0; i < cl.worldmodel->numnodes; i++ ) cl.worldmodel->nodes[i].visframe = tr.visframecount; return; } // 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; Q_memcpy( 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 ); } } } /* ======================== GL_CreateSurfaceLightmap ======================== */ void GL_CreateSurfaceLightmap( msurface_t *surf ) { int smax, tmax; byte *base; if( !cl.worldmodel->lightdata ) return; if( surf->flags & SURF_DRAWTILED ) return; smax = ( surf->extents[0] >> 4 ) + 1; tmax = ( surf->extents[1] >> 4 ) + 1; if( !LM_AllocBlock( smax, tmax, &surf->light_s, &surf->light_t )) { LM_UploadBlock( false ); LM_InitBlock(); if( !LM_AllocBlock( smax, tmax, &surf->light_s, &surf->light_t )) Host_Error( "AllocBlock: full\n" ); } surf->lightmaptexturenum = gl_lms.current_lightmap_texture; base = gl_lms.lightmap_buffer; base += ( surf->light_t * BLOCK_WIDTH + surf->light_s ) * 4; R_SetCacheState( surf ); R_BuildLightMap( surf, base, BLOCK_WIDTH * 4 ); } /* ================== GL_RebuildLightmaps Rebuilds the lightmap texture when gamma is changed ================== */ void GL_RebuildLightmaps( void ) { int i, j; model_t *m; if( !cl.world ) return; // wait for worldmodel vid_gamma->modified = false; // release old lightmaps for( i = 0; i < MAX_LIGHTMAPS; i++ ) { if( !tr.lightmapTextures[i] ) break; GL_FreeTexture( tr.lightmapTextures[i] ); } Q_memset( tr.lightmapTextures, 0, sizeof( tr.lightmapTextures )); gl_lms.current_lightmap_texture = 0; // setup all the lightstyles R_AnimateLight(); LM_InitBlock(); for( i = 1; i < MAX_MODELS; i++ ) { if(( m = Mod_Handle( i )) == NULL ) continue; if( m->name[0] == '*' || m->type != mod_brush ) continue; for( j = 0; j < m->numsurfaces; j++ ) GL_CreateSurfaceLightmap( m->surfaces + j ); } LM_UploadBlock( false ); if( clgame.drawFuncs.GL_BuildLightmaps ) { // build lightmaps on the client-side clgame.drawFuncs.GL_BuildLightmaps( ); } } /* ================== GL_BuildLightmaps Builds the lightmap texture with all the surfaces from all brush models ================== */ void GL_BuildLightmaps( void ) { int i, j; model_t *m; // release old lightmaps for( i = 0; i < MAX_LIGHTMAPS; i++ ) { if( !tr.lightmapTextures[i] ) break; GL_FreeTexture( tr.lightmapTextures[i] ); } // release old mirror textures for( i = 0; i < MAX_MIRRORS; i++ ) { if( !tr.mirrorTextures[i] ) break; GL_FreeTexture( tr.mirrorTextures[i] ); } Q_memset( tr.lightmapTextures, 0, sizeof( tr.lightmapTextures )); Q_memset( tr.mirror_entities, 0, sizeof( tr.mirror_entities )); Q_memset( tr.mirrorTextures, 0, sizeof( tr.mirrorTextures )); Q_memset( visbytes, 0x00, sizeof( visbytes )); skychain = NULL; tr.framecount = tr.visframecount = 1; // no dlight cache gl_lms.current_lightmap_texture = 0; tr.num_mirror_entities = 0; tr.num_mirrors_used = 0; // setup all the lightstyles R_AnimateLight(); LM_InitBlock(); for( i = 1; i < MAX_MODELS; i++ ) { if(( m = Mod_Handle( i )) == NULL ) continue; if( m->name[0] == '*' || m->type != mod_brush ) continue; for( j = 0; j < m->numsurfaces; j++ ) { // clearing all decal chains m->surfaces[j].pdecals = NULL; m->surfaces[j].visframe = 0; GL_CreateSurfaceLightmap( m->surfaces + j ); if( m->surfaces[j].flags & SURF_DRAWTURB ) continue; if( m->surfaces[j].flags & SURF_DRAWSKY && world.sky_sphere ) continue; GL_BuildPolygonFromSurface( m, m->surfaces + j ); } // clearing visframe for( j = 0; j < m->numleafs; j++ ) m->leafs[j+1].visframe = 0; for( j = 0; j < m->numnodes; j++ ) m->nodes[j].visframe = 0; } LM_UploadBlock( false ); if( clgame.drawFuncs.GL_BuildLightmaps ) { // build lightmaps on the client-side clgame.drawFuncs.GL_BuildLightmaps( ); } }