/* gl_lightmap.cpp - generate lightmaps and uploading them Copyright (C) 2016 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 "hud.h" #include "cl_util.h" #include "const.h" #include "studio.h" #include "com_model.h" #include "ref_params.h" #include "gl_local.h" #include #include "gl_shader.h" #include "gl_world.h" /* ============================================================================= LIGHTMAP ALLOCATION ============================================================================= */ static int LM_AllocBlock( unsigned short w, unsigned short h, unsigned short *x, unsigned short *y ) { gl_lightmap_t *lms = &tr.lightmaps[tr.current_lightmap_texture]; unsigned short i, j, best, best2; best = BLOCK_SIZE; for( i = 0; i < BLOCK_SIZE - w; i++ ) { best2 = 0; for( j = 0; j < w; j++ ) { if( lms->allocated[i+j] >= best ) break; if( lms->allocated[i+j] > best2 ) best2 = lms->allocated[i+j]; } if( j == w ) { // this is a valid spot *x = i; *y = best = best2; } } if( best + h > BLOCK_SIZE ) { // current lightmap is full lms->state = LM_DONE; return false; } for( i = 0; i < w; i++ ) lms->allocated[*x + i] = best + h; lms->state = LM_USED; // lightmap in use return true; } static void LM_InitBlock( void ) { word dummy; gl_lightmap_t *lms = &tr.lightmaps[tr.current_lightmap_texture]; memset( lms->allocated, 0, sizeof( lms->allocated )); // first block at pos 0,0 used as black lightmap for studiomodel LM_AllocBlock( 1, 1, &dummy, &dummy ); } static void LM_UploadPages( bool lightmap, bool deluxmap ) { char lmName[16]; byte lightBuf[4]; byte deluxBuf[4]; int i; lightBuf[0] = 0; lightBuf[1] = 0; lightBuf[2] = 0; lightBuf[3] = 0; deluxBuf[0] = 127; deluxBuf[1] = 127; deluxBuf[2] = 255; deluxBuf[3] = 0; for( i = 0; i < MAX_LIGHTMAPS && tr.lightmaps[i].state != LM_FREE; i++ ) { gl_lightmap_t *lms = &tr.lightmaps[i]; if( lightmap && !lms->lightmap ) { Q_snprintf( lmName, sizeof( lmName ), "*diffuse%i", i ); lms->lightmap = CREATE_TEXTURE( lmName, BLOCK_SIZE, BLOCK_SIZE, NULL, TF_LIGHTMAP ); // also loading dummy blackpixel GL_BindTexture( GL_TEXTURE0, lms->lightmap ); pglTexSubImage2D( GL_TEXTURE_2D, 0, 0, 0, 1, 1, GL_RGBA, GL_UNSIGNED_BYTE, lightBuf ); } if( deluxmap && !lms->deluxmap ) { Q_snprintf( lmName, sizeof( lmName ), "*normals%i", i ); lms->deluxmap = CREATE_TEXTURE( lmName, BLOCK_SIZE, BLOCK_SIZE, NULL, TF_DELUXMAP ); // also loading dummy blackpixel GL_BindTexture( GL_TEXTURE0, lms->deluxmap ); pglTexSubImage2D( GL_TEXTURE_2D, 0, 0, 0, 1, 1, GL_RGBA, GL_UNSIGNED_BYTE, deluxBuf ); } } } static void LM_GoToNextPage( void ) { gl_lightmap_t *lms = &tr.lightmaps[tr.current_lightmap_texture]; if( lms->state != LM_DONE ) return; // current atlas not completed if( ++tr.current_lightmap_texture == MAX_LIGHTMAPS ) HOST_ERROR( "MAX_LIGHTMAPS limit exceded\n" ); } /* ================== GL_BeginBuildingLightmaps ================== */ void GL_BeginBuildingLightmaps( void ) { int i; // release old lightmaps first for( i = 0; i < MAX_LIGHTMAPS && tr.lightmaps[i].state != LM_FREE; i++ ) { FREE_TEXTURE( tr.lightmaps[i].lightmap ); FREE_TEXTURE( tr.lightmaps[i].deluxmap ); } memset( tr.lightmaps, 0, sizeof( tr.lightmaps )); tr.current_lightmap_texture = 0; LM_InitBlock(); } /* ================= Mod_AllocLightmapForFace NOTE: we don't loading lightmap here. just create lmcoords and set lmnum ================= */ void GL_AllocLightmapForFace( msurface_t *surf ) { mextrasurf_t *esrf = surf->info; word smax, tmax; int map; // always reject the tiled faces if( FBitSet( surf->flags, SURF_DRAWSKY )) return; // no lightdata and no deluxdata if( !surf->samples && !esrf->normals ) return; int sample_size = Mod_SampleSizeForFace( surf ); smax = ( surf->info->lightextents[0] / sample_size ) + 1; tmax = ( surf->info->lightextents[1] / sample_size ) + 1; new_page: // alloc blocks for all the styles on current page for( map = 0; map < MAXLIGHTMAPS && surf->styles[map] != LS_NONE; map++ ) { if( !LM_AllocBlock( smax, tmax, &esrf->light_s[map], &esrf->light_t[map] )) { // current page is not enough room for next 1-4 blocks LM_GoToNextPage(); LM_InitBlock(); goto new_page; } } // lightmap will be uploaded as far as player can see it esrf->lightmaptexturenum = tr.current_lightmap_texture; SetBits( surf->flags, SURF_LM_UPDATE|SURF_DM_UPDATE ); } /* ================= Mod_AllocLightmapForFace NOTE: we don't loading lightmap here. just create lmcoords and set lmnum ================= */ bool GL_AllocLightmapForFace( mstudiosurface_t *surf ) { word smax, tmax; int map; smax = surf->lightextents[0] + 1; tmax = surf->lightextents[1] + 1; // alloc blocks for all the styles on current page for( map = 0; map < MAXLIGHTMAPS && surf->styles[map] != LS_NONE; map++ ) { if( !LM_AllocBlock( smax, tmax, &surf->light_s[map], &surf->light_t[map] )) { // current page is not enough room for next 1-4 blocks LM_GoToNextPage(); LM_InitBlock(); return false; } } // lightmap will be uploaded as far as player can see it surf->lightmaptexturenum = tr.current_lightmap_texture; SetBits( surf->flags, SURF_LM_UPDATE|SURF_DM_UPDATE ); return true; } /* ======================= GL_EndBuildingLightmaps ======================= */ void GL_EndBuildingLightmaps( bool lightmap, bool deluxmap ) { LM_UploadPages( lightmap, deluxmap ); } /* ================= R_BuildLightMapForStyle write lightmap into page for a given style ================= */ static void R_BuildLightMapForStyle( msurface_t *surf, byte *dest, int style ) { mextrasurf_t *esrf = surf->info; int stride, size; int smax, tmax; int s, t; color24 *lm; byte *sm; ASSERT( style >= 0 && style < MAXLIGHTMAPS ); // always reject the sky faces if( FBitSet( surf->flags, SURF_DRAWSKY )) return; // no lightdata or style missed if( !surf->samples || surf->styles[style] == LS_NONE ) return; int sample_size = Mod_SampleSizeForFace( surf ); smax = ( surf->info->lightextents[0] / sample_size ) + 1; tmax = ( surf->info->lightextents[1] / sample_size ) + 1; size = smax * tmax; // jump to specified style lm = surf->samples + size * style; sm = esrf->shadows + size * style; // put into texture format stride = (smax * 4) - (smax << 2); for( t = 0; t < tmax; t++, dest += stride ) { for( s = 0; s < smax; s++ ) { dest[0] = TEXTURE_TO_TEXGAMMA( lm->r ); dest[1] = TEXTURE_TO_TEXGAMMA( lm->g ); dest[2] = TEXTURE_TO_TEXGAMMA( lm->b ); if( esrf->shadows != NULL ) dest[3] = *sm++; else dest[3] = 255; dest += 4; lm++; } } } /* ================= R_BuildLightMapForStyle write lightmap into page for a given style ================= */ static void R_BuildLightMapForStyle( mstudiosurface_t *surf, byte *dest, int style ) { int stride, size; int smax, tmax; int s, t; color24 *lm; byte *sm; ASSERT( style >= 0 && style < MAXLIGHTMAPS ); // no lightdata or style missed if( !surf->samples || surf->styles[style] == LS_NONE ) return; smax = surf->lightextents[0] + 1; tmax = surf->lightextents[1] + 1; size = smax * tmax; // jump to specified style lm = surf->samples + size * style; sm = surf->shadows + size * style; // put into texture format stride = (smax * 4) - (smax << 2); for( t = 0; t < tmax; t++, dest += stride ) { for( s = 0; s < smax; s++ ) { dest[0] = TEXTURE_TO_TEXGAMMA( lm->r ); dest[1] = TEXTURE_TO_TEXGAMMA( lm->g ); dest[2] = TEXTURE_TO_TEXGAMMA( lm->b ); if( surf->shadows != NULL ) dest[3] = *sm++; else dest[3] = 255; dest += 4; lm++; } } } /* ================= R_BuildDeluxMapForStyle write deluxmap into page for a given style ================= */ static void R_BuildDeluxMapForStyle( msurface_t *surf, byte *dest, int style ) { mextrasurf_t *esrf = surf->info; int stride, size; int smax, tmax; int s, t; color24 *dm; ASSERT( style >= 0 && style < MAXLIGHTMAPS ); // always reject the sky faces if( FBitSet( surf->flags, SURF_DRAWSKY )) return; // no lightdata or style missed if( !esrf->normals || surf->styles[style] == LS_NONE ) return; int sample_size = Mod_SampleSizeForFace( surf ); smax = ( surf->info->lightextents[0] / sample_size ) + 1; tmax = ( surf->info->lightextents[1] / sample_size ) + 1; size = smax * tmax; // jump to specified style dm = esrf->normals + size * style; // put into texture format stride = (smax * 4) - (smax << 2); for( t = 0; t < tmax; t++, dest += stride ) { for( s = 0; s < smax; s++ ) { dest[0] = dm->r; dest[1] = dm->g; dest[2] = dm->b; dest[3] = 255; dest += 4; dm++; } } } /* ================= R_BuildDeluxMapForStyle write deluxmap into page for a given style ================= */ static void R_BuildDeluxMapForStyle( mstudiosurface_t *surf, byte *dest, int style ) { int stride, size; int smax, tmax; int s, t; color24 *dm; ASSERT( style >= 0 && style < MAXLIGHTMAPS ); // no lightdata or style missed if( !surf->normals || surf->styles[style] == LS_NONE ) return; smax = surf->lightextents[0] + 1; tmax = surf->lightextents[1] + 1; size = smax * tmax; // jump to specified style dm = surf->normals + size * style; // put into texture format stride = (smax * 4) - (smax << 2); for( t = 0; t < tmax; t++, dest += stride ) { for( s = 0; s < smax; s++ ) { dest[0] = dm->r; dest[1] = dm->g; dest[2] = dm->b; dest[3] = 255; dest += 4; dm++; } } } /* ================= R_UpdateLightMap Combine and scale multiple lightmaps into the floating format in r_blocklights ================= */ static void R_UpdateLightMap( msurface_t *surf ) { mextrasurf_t *esrf = surf->info; static byte buf[132*132*4]; int map, smax, tmax; // always reject the sky faces if( FBitSet( surf->flags, SURF_DRAWSKY )) return; int sample_size = Mod_SampleSizeForFace( surf ); smax = ( surf->info->lightextents[0] / sample_size ) + 1; tmax = ( surf->info->lightextents[1] / sample_size ) + 1; // upload the lightmap if( surf->samples != NULL && FBitSet( surf->flags, SURF_LM_UPDATE )) { GL_BindTexture( GL_TEXTURE0, tr.lightmaps[esrf->lightmaptexturenum].lightmap ); // write lightmaps into page for( map = 0; map < MAXLIGHTMAPS && surf->styles[map] != LS_NONE; map++ ) { R_BuildLightMapForStyle( surf, buf, map ); pglTexSubImage2D( GL_TEXTURE_2D, 0, esrf->light_s[map], esrf->light_t[map], smax, tmax, GL_RGBA, GL_UNSIGNED_BYTE, buf ); } } ClearBits( surf->flags, SURF_LM_UPDATE ); // upload the deluxemap if( esrf->normals != NULL && FBitSet( surf->flags, SURF_DM_UPDATE )) { GL_BindTexture( GL_TEXTURE0, tr.lightmaps[esrf->lightmaptexturenum].deluxmap ); // write lightmaps into page for( map = 0; map < MAXLIGHTMAPS && surf->styles[map] != LS_NONE; map++ ) { R_BuildDeluxMapForStyle( surf, buf, map ); pglTexSubImage2D( GL_TEXTURE_2D, 0, esrf->light_s[map], esrf->light_t[map], smax, tmax, GL_RGBA, GL_UNSIGNED_BYTE, buf ); } } ClearBits( surf->flags, SURF_DM_UPDATE ); } /* ================= R_UpdateLightMap Combine and scale multiple lightmaps into the floating format in r_blocklights ================= */ static void R_UpdateLightMap( mstudiosurface_t *surf ) { static byte buf[512*512*4]; int map, smax, tmax; smax = surf->lightextents[0] + 1; tmax = surf->lightextents[1] + 1; // upload the lightmap if( surf->samples != NULL && FBitSet( surf->flags, SURF_LM_UPDATE )) { GL_BindTexture( GL_TEXTURE0, tr.lightmaps[surf->lightmaptexturenum].lightmap ); // write lightmaps into page for( map = 0; map < MAXLIGHTMAPS && surf->styles[map] != LS_NONE; map++ ) { R_BuildLightMapForStyle( surf, buf, map ); pglTexSubImage2D( GL_TEXTURE_2D, 0, surf->light_s[map], surf->light_t[map], smax, tmax, GL_RGBA, GL_UNSIGNED_BYTE, buf ); } } ClearBits( surf->flags, SURF_LM_UPDATE ); // upload the deluxemap if( surf->normals != NULL && FBitSet( surf->flags, SURF_DM_UPDATE )) { GL_BindTexture( GL_TEXTURE0, tr.lightmaps[surf->lightmaptexturenum].deluxmap ); // write lightmaps into page for( map = 0; map < MAXLIGHTMAPS && surf->styles[map] != LS_NONE; map++ ) { R_BuildDeluxMapForStyle( surf, buf, map ); pglTexSubImage2D( GL_TEXTURE_2D, 0, surf->light_s[map], surf->light_t[map], smax, tmax, GL_RGBA, GL_UNSIGNED_BYTE, buf ); } } ClearBits( surf->flags, SURF_DM_UPDATE ); } /* ======================== R_TextureCoords fill vec2_t with texture coords ======================== */ void R_TextureCoords( msurface_t *surf, const Vector &vec, float *out ) { float s, t; s = DotProduct( vec, surf->texinfo->vecs[0] ) + surf->texinfo->vecs[0][3]; s /= surf->texinfo->texture->width; t = DotProduct( vec, surf->texinfo->vecs[1] ) + surf->texinfo->vecs[1][3]; t /= surf->texinfo->texture->height; out[0] = s; out[1] = t; } /* ======================== R_GlobalCoords fill vec2_t with global coords ======================== */ void R_GlobalCoords( msurface_t *surf, const Vector &point, float *out ) { mfaceinfo_t *land = surf->texinfo->faceinfo; terrain_t *terra; Vector size; indexMap_t *im; if( !land ) return; terra = land->terrain; if( !terra ) return; im = &terra->indexmap; for( int i = 0; i < 3; i++ ) size[i] = land->maxs[i] - land->mins[i]; out[2] = ( point[0] - land->mins[0] ) / size[0]; out[3] = ( land->maxs[1] - point[1] ) / size[1]; } /* ======================== R_GlobalCoords fill vec2_t with global coords ======================== */ void R_GlobalCoords( msurface_t *surf, const Vector &point, const Vector &absmin, const Vector &absmax, float scale, float *out ) { Vector size; for( int i = 0; i < 3; i++ ) size[i] = absmax[i] - absmin[i]; out[2] = (( point[0] - absmin[0] ) / size[0]) * scale; out[3] = (( absmax[1] - point[1] ) / size[1]) * scale; } /* ======================== R_LightmapCoords fill Vector4D with lightstyle coords (two styles per array) ======================== */ void R_LightmapCoords( msurface_t *surf, const Vector &vec, float *coords, int style ) { mextrasurf_t *esrf = surf->info; float sample_size = Mod_SampleSizeForFace( surf ); float s, t; for( int i = 0; i < 2; i++ ) { if( surf->styles[style+i] == LS_NONE ) return; // end of styles s = DotProduct( vec, surf->info->lmvecs[0] ) + surf->info->lmvecs[0][3]; s -= surf->info->lightmapmins[0]; s += esrf->light_s[style+i] * sample_size; s += sample_size * 0.5f; s /= BLOCK_SIZE * sample_size; t = DotProduct( vec, surf->info->lmvecs[1] ) + surf->info->lmvecs[1][3]; t -= surf->info->lightmapmins[1]; t += esrf->light_t[style+i] * sample_size; t += sample_size * 0.5f; t /= BLOCK_SIZE * sample_size; coords[i*2+0] = s; coords[i*2+1] = t; } } /* ======================== R_LightmapCoords fill Vector4D with lightstyle coords (two styles per array) ======================== */ void R_LightmapCoords( mstudiosurface_t *surf, const Vector &vec, const Vector lmvecs[2], float *coords, int style ) { float s, t; for( int i = 0; i < 2; i++ ) { if( surf->styles[style+i] == LS_NONE ) return; // end of styles s = DotProduct( vec, lmvecs[0] ) + surf->light_s[style+i] + 0.5f; t = DotProduct( vec, lmvecs[1] ) + surf->light_t[style+i] + 0.5f; s /= (float)BLOCK_SIZE; t /= (float)BLOCK_SIZE; coords[i*2+0] = s; coords[i*2+1] = t; } } /* ================= R_UpdateSurfaceParams update some surface params if this was changed ================= */ void R_UpdateSurfaceParams( msurface_t *surf ) { mextrasurf_t *esrf = surf->info; cl_entity_t *e = RI->currententity; model_t *clmodel = e->model; // check for lightmap modification if( FBitSet( surf->flags, SURF_LM_UPDATE|SURF_DM_UPDATE )) R_UpdateLightMap( surf ); if( FBitSet( surf->flags, SURF_MOVIE )) R_UpdateCinematic( surf ); // handle conveyor movement if( FBitSet( clmodel->flags, BIT( 0 )) && FBitSet( surf->flags, SURF_CONVEYOR )) { float flRate, flAngle; float flWidth, flConveyorSpeed; float sOffset, sy; float tOffset, cy; flConveyorSpeed = (e->curstate.rendercolor.g<<8|e->curstate.rendercolor.b) / 16.0f; if( e->curstate.rendercolor.r ) flConveyorSpeed = -flConveyorSpeed; flWidth = (float)RENDER_GET_PARM( PARM_TEX_SRC_WIDTH, surf->texinfo->texture->gl_texturenum ); if( flWidth != 0.0f ) { flRate = abs( flConveyorSpeed ) / flWidth; flAngle = ( flConveyorSpeed >= 0.0f ) ? 180.0f : 0.0f; SinCos( DEG2RAD( flAngle ), &sy, &cy ); sOffset = tr.time * cy * flRate; tOffset = tr.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; esrf->texofs[0] = sOffset; esrf->texofs[1] = tOffset; } else { // no conveyor esrf->texofs[0] = 0.0f; esrf->texofs[1] = 0.0f; } } else { // no conveyor esrf->texofs[0] = 0.0f; esrf->texofs[1] = 0.0f; } } /* ================= R_UpdateSurfaceParams update some surface params if this was changed ================= */ void R_UpdateSurfaceParams( mstudiosurface_t *surf ) { // check for lightmap modification if( FBitSet( surf->flags, SURF_LM_UPDATE|SURF_DM_UPDATE )) R_UpdateLightMap( surf ); }