forked from FWGS/Paranoia2
1202 lines
30 KiB
C++
1202 lines
30 KiB
C++
/*
|
|
gl_rmisc.cpp - renderer miscellaneous
|
|
Copyright (C) 2013 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 "gl_local.h"
|
|
#include "gl_studio.h"
|
|
#include "gl_aurora.h"
|
|
#include "gl_rpart.h"
|
|
#include "material.h"
|
|
#include "gl_occlusion.h"
|
|
#include "gl_world.h"
|
|
#include "gl_grass.h"
|
|
#include "gl_shader.h"
|
|
|
|
#define DEFAULT_SMOOTHNESS 0.35f
|
|
#define FILTER_SIZE 2
|
|
|
|
/*
|
|
==================
|
|
CL_LoadMaterials
|
|
|
|
parse material from a given file
|
|
==================
|
|
*/
|
|
void CL_LoadMaterials( const char *path )
|
|
{
|
|
ALERT( at_aiconsole, "loading %s\n", path );
|
|
|
|
char *afile = (char *)gEngfuncs.COM_LoadFile( (char *)path, 5, NULL );
|
|
|
|
if( !afile )
|
|
{
|
|
ALERT( at_error, "Cannot open file \"%s\"\n", path );
|
|
return;
|
|
}
|
|
|
|
matdesc_t *oldmaterials = tr.materials;
|
|
int oldcount = tr.matcount;
|
|
char *pfile = afile;
|
|
char token[256];
|
|
int depth = 0;
|
|
float flValue;
|
|
|
|
// count materials
|
|
while( pfile != NULL )
|
|
{
|
|
pfile = COM_ParseFile( pfile, token );
|
|
if( !pfile ) break;
|
|
|
|
if( Q_strlen( token ) > 1 )
|
|
continue;
|
|
|
|
if( token[0] == '{' )
|
|
{
|
|
depth++;
|
|
}
|
|
else if( token[0] == '}' )
|
|
{
|
|
tr.matcount++;
|
|
depth--;
|
|
}
|
|
}
|
|
|
|
if( depth > 0 ) ALERT( at_warning, "%s: EOF reached without closing brace\n", path );
|
|
if( depth < 0 ) ALERT( at_warning, "%s: EOF reached without opening brace\n", path );
|
|
|
|
tr.materials = (matdesc_t *)Mem_Alloc( sizeof( matdesc_t ) * tr.matcount );
|
|
memcpy( tr.materials, oldmaterials, oldcount * sizeof( matdesc_t ));
|
|
Mem_Free( oldmaterials );
|
|
pfile = afile; // start real parsing
|
|
|
|
int current = oldcount; // starts from
|
|
|
|
while( pfile != NULL )
|
|
{
|
|
pfile = COM_ParseFile( pfile, token );
|
|
if( !pfile ) break;
|
|
|
|
if( current >= tr.matcount )
|
|
{
|
|
ALERT ( at_error, "material parse is overrun %d > %d\n", current, tr.matcount );
|
|
break;
|
|
}
|
|
|
|
matdesc_t *mat = &tr.materials[current];
|
|
|
|
// read the material name
|
|
Q_strncpy( mat->name, token, sizeof( mat->name ));
|
|
COM_StripExtension( mat->name );
|
|
|
|
for( int i = 0; i < current; i++ )
|
|
{
|
|
if( !Q_stricmp( mat->name, tr.materials[i].name ))
|
|
{
|
|
ALERT( at_warning, "mat %s was duplicated, second definition will be ignored\n", mat->name );
|
|
break;
|
|
}
|
|
}
|
|
|
|
// set defaults
|
|
mat->dt_texturenum = tr.grayTexture;
|
|
mat->detailScale[0] = 10.0f;
|
|
mat->detailScale[1] = 10.0f;
|
|
mat->smoothness = DEFAULT_SMOOTHNESS;
|
|
|
|
// read opening brace
|
|
pfile = COM_ParseFile( pfile, token );
|
|
if( !pfile ) break;
|
|
|
|
if( token[0] != '{' )
|
|
{
|
|
ALERT( at_error, "found %s when expecting {\n", token );
|
|
break;
|
|
}
|
|
|
|
while( pfile != NULL )
|
|
{
|
|
pfile = COM_ParseFile( pfile, token );
|
|
if( !pfile )
|
|
{
|
|
ALERT( at_error, "EOF without closing brace\n" );
|
|
goto getout;
|
|
}
|
|
|
|
// description end goto next material
|
|
if( token[0] == '}' )
|
|
{
|
|
current++;
|
|
break;
|
|
}
|
|
else if( !Q_stricmp( token, "glossExp" )) // for backward compatibility
|
|
{
|
|
pfile = COM_ParseFile( pfile, token );
|
|
if( !pfile )
|
|
{
|
|
ALERT( at_error, "hit EOF while parsing 'glossExp'\n" );
|
|
goto getout;
|
|
}
|
|
|
|
flValue = Q_atof( token );
|
|
flValue = bound( 0.0f, flValue, 256.0f );
|
|
mat->smoothness = sqrt( flValue / 256.0f );
|
|
}
|
|
else if( !Q_stricmp( token, "smoothness" ))
|
|
{
|
|
pfile = COM_ParseFile( pfile, token );
|
|
if( !pfile )
|
|
{
|
|
ALERT( at_error, "hit EOF while parsing 'smoothness'\n" );
|
|
goto getout;
|
|
}
|
|
|
|
mat->smoothness = Q_atof( token );
|
|
mat->smoothness = bound( 0.0f, mat->smoothness, 1.0f );
|
|
}
|
|
else if( !Q_stricmp( token, "detailScale" ))
|
|
{
|
|
pfile = COM_ParseFile( pfile, token );
|
|
if( !pfile )
|
|
{
|
|
ALERT( at_error, "hit EOF while parsing 'detailScale'\n" );
|
|
goto getout;
|
|
}
|
|
|
|
Q_atov( mat->detailScale, token, 2 );
|
|
mat->detailScale[0] = bound( 0.01f, mat->detailScale[0], 100.0f );
|
|
mat->detailScale[1] = bound( 0.01f, mat->detailScale[0], 100.0f );
|
|
}
|
|
else if( !Q_stricmp( token, "detailmap" ))
|
|
{
|
|
pfile = COM_ParseFile( pfile, token );
|
|
if( !pfile )
|
|
{
|
|
ALERT( at_error, "hit EOF while parsing 'detailmap'\n" );
|
|
goto getout;
|
|
}
|
|
|
|
COM_FixSlashes( token );
|
|
mat->dt_texturenum = LOAD_TEXTURE( token, NULL, 0, TF_FORCE_COLOR );
|
|
}
|
|
else if( !Q_stricmp( token, "diffuseMap" ))
|
|
{
|
|
pfile = COM_ParseFile( pfile, token );
|
|
if( !pfile )
|
|
{
|
|
ALERT( at_error, "hit EOF while parsing 'diffusemap'\n" );
|
|
goto getout;
|
|
}
|
|
|
|
COM_FixSlashes( token );
|
|
Q_strncpy( mat->diffusemap, token, sizeof( mat->diffusemap ));
|
|
}
|
|
else if( !Q_stricmp( token, "normalMap" ))
|
|
{
|
|
pfile = COM_ParseFile( pfile, token );
|
|
if( !pfile )
|
|
{
|
|
ALERT( at_error, "hit EOF while parsing 'normalmap'\n" );
|
|
goto getout;
|
|
}
|
|
|
|
COM_FixSlashes( token );
|
|
Q_strncpy( mat->normalmap, token, sizeof( mat->normalmap ));
|
|
}
|
|
else if( !Q_stricmp( token, "glossMap" ))
|
|
{
|
|
pfile = COM_ParseFile( pfile, token );
|
|
if( !pfile )
|
|
{
|
|
ALERT( at_error, "hit EOF while parsing 'glossmap'\n" );
|
|
goto getout;
|
|
}
|
|
|
|
COM_FixSlashes( token );
|
|
Q_strncpy( mat->glossmap, token, sizeof( mat->glossmap ));
|
|
}
|
|
else if( !Q_stricmp( token, "AberrationScale" ))
|
|
{
|
|
pfile = COM_ParseFile( pfile, token );
|
|
if( !pfile )
|
|
{
|
|
ALERT( at_error, "hit EOF while parsing 'AberrationScale'\n" );
|
|
goto getout;
|
|
}
|
|
|
|
mat->aberrationScale = Q_atof( token ) * 0.1f;
|
|
mat->aberrationScale = bound( 0.0f, mat->aberrationScale, 0.1f );
|
|
}
|
|
else if( !Q_stricmp( token, "ReflectScale" ))
|
|
{
|
|
pfile = COM_ParseFile( pfile, token );
|
|
if( !pfile )
|
|
{
|
|
ALERT( at_error, "hit EOF while parsing 'ReflectScale'\n" );
|
|
goto getout;
|
|
}
|
|
|
|
mat->reflectScale = Q_atof( token );
|
|
mat->reflectScale = bound( 0.0f, mat->reflectScale, 1.0f );
|
|
}
|
|
else if( !Q_stricmp( token, "RefractScale" ))
|
|
{
|
|
pfile = COM_ParseFile( pfile, token );
|
|
if( !pfile )
|
|
{
|
|
ALERT( at_error, "hit EOF while parsing 'RefractScale'\n" );
|
|
goto getout;
|
|
}
|
|
|
|
mat->refractScale = Q_atof( token );
|
|
mat->refractScale = bound( 0.0f, mat->refractScale, 1.0f );
|
|
}
|
|
else if( !Q_stricmp( token, "ReliefScale" ))
|
|
{
|
|
pfile = COM_ParseFile( pfile, token );
|
|
if( !pfile )
|
|
{
|
|
ALERT( at_error, "hit EOF while parsing 'ReliefScale'\n" );
|
|
goto getout;
|
|
}
|
|
|
|
mat->reliefScale = Q_atof( token );
|
|
mat->reliefScale = bound( 0.0f, mat->reliefScale, 1.0f );
|
|
}
|
|
else if( !Q_stricmp( token, "material" ))
|
|
{
|
|
pfile = COM_ParseFile( pfile, token );
|
|
if( !pfile )
|
|
{
|
|
ALERT( at_error, "hit EOF while parsing 'material'\n" );
|
|
goto getout;
|
|
}
|
|
|
|
mat->effects = COM_FindMatdef( token );
|
|
}
|
|
else ALERT( at_warning, "Unknown material token %s\n", token );
|
|
}
|
|
|
|
// apply default values
|
|
if( !mat->effects ) mat->effects = COM_DefaultMatdef();
|
|
|
|
if( mat->dt_texturenum == tr.grayTexture && mat->effects->detailName[0] )
|
|
{
|
|
mat->dt_texturenum = LOAD_TEXTURE( mat->effects->detailName, NULL, 0, TF_FORCE_COLOR );
|
|
mat->detailScale[0] = mat->effects->detailScale[0];
|
|
mat->detailScale[1] = mat->effects->detailScale[0];
|
|
}
|
|
|
|
// if detail texture was missed
|
|
if( mat->dt_texturenum == 0 )
|
|
mat->dt_texturenum = tr.grayTexture;
|
|
}
|
|
getout:
|
|
gEngfuncs.COM_FreeFile( afile );
|
|
ALERT( at_aiconsole, "%d materials parsed\n", current );
|
|
}
|
|
|
|
/*
|
|
==================
|
|
CL_InitMaterials
|
|
|
|
parse all files with .mat extension
|
|
==================
|
|
*/
|
|
void CL_InitMaterials( void )
|
|
{
|
|
int count = 0;
|
|
char **filenames = FS_SEARCH( "scripts/*.mat", &count, 0 );
|
|
|
|
tr.matcount = 0;
|
|
|
|
// sequentially load materials
|
|
for( int i = 0; i < count; i++ )
|
|
CL_LoadMaterials( filenames[i] );
|
|
|
|
}
|
|
|
|
/*
|
|
==================
|
|
CL_FindMaterial
|
|
|
|
This function never failed
|
|
==================
|
|
*/
|
|
matdesc_t *CL_FindMaterial( const char *name )
|
|
{
|
|
static matdesc_t defmat;
|
|
|
|
if( !defmat.name[0] )
|
|
{
|
|
// initialize default material
|
|
Q_strncpy( defmat.name, "*default", sizeof( defmat.name ));
|
|
defmat.dt_texturenum = tr.grayTexture;
|
|
defmat.detailScale[0] = 10.0f;
|
|
defmat.detailScale[1] = 10.0f;
|
|
defmat.aberrationScale = 0.0f;
|
|
defmat.reflectScale = 0.0f;
|
|
defmat.refractScale = 0.0f;
|
|
defmat.reliefScale = 0.0f;
|
|
defmat.smoothness = DEFAULT_SMOOTHNESS; // same as glossExp 32.0
|
|
defmat.effects = COM_DefaultMatdef();
|
|
}
|
|
|
|
for( int i = 0; i < tr.matcount; i++ )
|
|
{
|
|
if( !Q_stricmp( name, tr.materials[i].name ))
|
|
return &tr.materials[i];
|
|
}
|
|
|
|
return &defmat;
|
|
}
|
|
|
|
/*
|
|
========================
|
|
LoadHeightMap
|
|
|
|
parse heightmap pixels and remap it
|
|
to real layer count
|
|
========================
|
|
*/
|
|
bool LoadHeightMap( indexMap_t *im, int numLayers )
|
|
{
|
|
unsigned int *src;
|
|
int i, tex;
|
|
int depth = 1;
|
|
|
|
if( !GL_Support( R_TEXTURE_ARRAY_EXT ) || numLayers <= 0 )
|
|
return false;
|
|
|
|
// loading heightmap and keep the source pixels
|
|
if( !( tex = LOAD_TEXTURE( im->name, NULL, 0, TF_KEEP_SOURCE|TF_EXPAND_SOURCE )))
|
|
return false;
|
|
|
|
if(( src = (unsigned int *)GET_TEXTURE_DATA( tex )) == NULL )
|
|
{
|
|
ALERT( at_error, "LoadHeightMap: couldn't get source pixels for %s\n", im->name );
|
|
FREE_TEXTURE( tex );
|
|
return false;
|
|
}
|
|
|
|
im->gl_diffuse_id = LOAD_TEXTURE( im->diffuse, NULL, 0, 0 );
|
|
|
|
int width = RENDER_GET_PARM( PARM_TEX_SRC_WIDTH, tex );
|
|
int height = RENDER_GET_PARM( PARM_TEX_SRC_HEIGHT, tex );
|
|
|
|
im->pixels = (byte *)Mem_Alloc( width * height );
|
|
im->numLayers = bound( 1, numLayers, 255 );
|
|
im->height = height;
|
|
im->width = width;
|
|
|
|
for( i = 0; i < ( im->width * im->height ); i++ )
|
|
{
|
|
byte rawHeight = ( src[i] & 0xFF );
|
|
im->maxHeight = Q_max(( 16 * (int)ceil( rawHeight / 16 )), im->maxHeight );
|
|
}
|
|
|
|
// merge layers count
|
|
im->numLayers = (im->maxHeight / 16) + 1;
|
|
depth = Q_max((int)Q_ceil((float)im->numLayers / 4.0f ), 1 );
|
|
|
|
// clamp to layers count
|
|
for( i = 0; i < ( im->width * im->height ); i++ )
|
|
im->pixels[i] = (( src[i] & 0xFF ) * ( im->numLayers - 1 )) / im->maxHeight;
|
|
|
|
size_t lay_size = im->width * im->height * 4;
|
|
size_t img_size = lay_size * depth;
|
|
byte *layers = (byte *)Mem_Alloc( img_size );
|
|
byte *pixels = (byte *)src;
|
|
|
|
for( int x = 0; x < im->width; x++ )
|
|
{
|
|
for( int y = 0; y < im->height; y++ )
|
|
{
|
|
float weights[MAX_LANDSCAPE_LAYERS];
|
|
|
|
memset( weights, 0, sizeof( weights ));
|
|
|
|
for( int pos_x = 0; pos_x < FILTER_SIZE; pos_x++ )
|
|
{
|
|
for( int pos_y = 0; pos_y < FILTER_SIZE; pos_y++ )
|
|
{
|
|
int img_x = (x - (FILTER_SIZE / 2) + pos_x + im->width) % im->width;
|
|
int img_y = (y - (FILTER_SIZE / 2) + pos_y + im->height) % im->height;
|
|
|
|
float rawHeight = (float)( src[img_y * im->width + img_x] & 0xFF );
|
|
float curLayer = ( rawHeight * ( im->numLayers - 1 )) / (float)im->maxHeight;
|
|
|
|
if( curLayer != (int)curLayer )
|
|
{
|
|
byte layer0 = (int)floor( curLayer );
|
|
byte layer1 = (int)ceil( curLayer );
|
|
float factor = curLayer - (int)curLayer;
|
|
weights[layer0] += (1.0 - factor) * (1.0 / (FILTER_SIZE * FILTER_SIZE));
|
|
weights[layer1] += (factor ) * (1.0 / (FILTER_SIZE * FILTER_SIZE));
|
|
}
|
|
else
|
|
{
|
|
weights[(int)curLayer] += (1.0 / (FILTER_SIZE * FILTER_SIZE));
|
|
}
|
|
}
|
|
}
|
|
|
|
// encode layers into RGBA channels
|
|
layers[lay_size * 0 + (y * im->width + x)*4+0] = weights[0] * 255;
|
|
layers[lay_size * 0 + (y * im->width + x)*4+1] = weights[1] * 255;
|
|
layers[lay_size * 0 + (y * im->width + x)*4+2] = weights[2] * 255;
|
|
layers[lay_size * 0 + (y * im->width + x)*4+3] = weights[3] * 255;
|
|
|
|
if( im->numLayers <= 4 ) continue;
|
|
|
|
layers[lay_size * 1 + ((y * im->width + x)*4+0)] = weights[4] * 255;
|
|
layers[lay_size * 1 + ((y * im->width + x)*4+1)] = weights[5] * 255;
|
|
layers[lay_size * 1 + ((y * im->width + x)*4+2)] = weights[6] * 255;
|
|
layers[lay_size * 1 + ((y * im->width + x)*4+3)] = weights[7] * 255;
|
|
|
|
if( im->numLayers <= 8 ) continue;
|
|
|
|
layers[lay_size * 2 + ((y * im->width + x)*4+0)] = weights[8] * 255;
|
|
layers[lay_size * 2 + ((y * im->width + x)*4+1)] = weights[9] * 255;
|
|
layers[lay_size * 2 + ((y * im->width + x)*4+2)] = weights[10] * 255;
|
|
layers[lay_size * 2 + ((y * im->width + x)*4+3)] = weights[11] * 255;
|
|
|
|
if( im->numLayers <= 12 ) continue;
|
|
|
|
layers[lay_size * 3 + ((y * im->width + x)*4+0)] = weights[12] * 255;
|
|
layers[lay_size * 3 + ((y * im->width + x)*4+1)] = weights[13] * 255;
|
|
layers[lay_size * 3 + ((y * im->width + x)*4+2)] = weights[14] * 255;
|
|
layers[lay_size * 3 + ((y * im->width + x)*4+3)] = weights[15] * 255;
|
|
}
|
|
}
|
|
|
|
// release source texture
|
|
FREE_TEXTURE( tex );
|
|
|
|
tex = CREATE_TEXTURE_ARRAY( im->name, im->width, im->height, depth, layers, TF_CLAMP|TF_HAS_ALPHA );
|
|
Mem_Free( layers );
|
|
|
|
im->gl_heightmap_id = tex;
|
|
|
|
return true;
|
|
}
|
|
|
|
/*
|
|
========================
|
|
LoadTerrainLayers
|
|
|
|
loading all the landscape layers
|
|
into texture arrays
|
|
========================
|
|
*/
|
|
bool LoadTerrainLayers( layerMap_t *lm, int numLayers )
|
|
{
|
|
char *texnames[MAX_LANDSCAPE_LAYERS];
|
|
char *ptr, buffer[1024], tmpname[64];
|
|
size_t nameLen = 64;
|
|
int i;
|
|
|
|
memset( buffer, 0, sizeof( buffer )); // list must be null terminated
|
|
|
|
// initialize names array
|
|
for( i = 0, ptr = buffer; i < MAX_LANDSCAPE_LAYERS; i++, ptr += nameLen )
|
|
texnames[i] = ptr;
|
|
|
|
// process diffuse textures
|
|
for( i = 0; i < numLayers; i++ )
|
|
{
|
|
Q_snprintf( texnames[i], nameLen, "textures/%s", lm->pathes[i] );
|
|
lm->material[i] = CL_FindMaterial( lm->names[i] );
|
|
lm->smoothness[i] = lm->material[i]->smoothness;
|
|
}
|
|
|
|
if(( lm->gl_diffuse_id = LOAD_TEXTURE_ARRAY( (const char **)texnames, 0 )) == 0 )
|
|
return false;
|
|
|
|
memset( buffer, 0, sizeof( buffer )); // list must be null terminated
|
|
|
|
// process normalmaps
|
|
for( i = 0; i < numLayers; i++ )
|
|
{
|
|
const char *ext = COM_FileExtension( lm->pathes[i] );
|
|
COM_FileBase( lm->pathes[i], tmpname );
|
|
|
|
if( !Q_stricmp( ext, "" ))
|
|
{
|
|
Q_snprintf( texnames[i], nameLen, "textures/%s_norm", tmpname );
|
|
if( IMAGE_EXISTS( texnames[i] ))
|
|
continue;
|
|
|
|
ALERT( at_warning, "layer normalmap %s is missed\n", texnames[i] );
|
|
break;
|
|
}
|
|
else
|
|
{
|
|
Q_snprintf( texnames[i], nameLen, "textures/%s_norm.%s", tmpname, ext );
|
|
if( FILE_EXISTS( texnames[i] ))
|
|
continue;
|
|
|
|
ALERT( at_warning, "layer normalmap %s is missed\n", texnames[i] );
|
|
break;
|
|
}
|
|
}
|
|
|
|
if( i == numLayers ) lm->gl_normalmap_id = LOAD_TEXTURE_ARRAY( (const char **)texnames, TF_NORMALMAP );
|
|
if( !lm->gl_normalmap_id ) lm->gl_normalmap_id = tr.normalmapTexture;
|
|
|
|
memset( buffer, 0, sizeof( buffer )); // list must be null terminated
|
|
|
|
// process glossmaps
|
|
for( i = 0; i < numLayers; i++ )
|
|
{
|
|
const char *ext = COM_FileExtension( lm->pathes[i] );
|
|
COM_FileBase( lm->pathes[i], tmpname );
|
|
|
|
if( !Q_stricmp( ext, "" ))
|
|
{
|
|
Q_snprintf( texnames[i], nameLen, "textures/%s_gloss", tmpname );
|
|
if( IMAGE_EXISTS( texnames[i] ))
|
|
continue;
|
|
|
|
ALERT( at_warning, "layer glossmap %s is missed\n", texnames[i] );
|
|
break;
|
|
}
|
|
else
|
|
{
|
|
Q_snprintf( texnames[i], nameLen, "textures/%s_gloss.%s", tmpname, ext );
|
|
if( FILE_EXISTS( texnames[i] ))
|
|
continue;
|
|
|
|
ALERT( at_warning, "layer glossmap %s is missed\n", texnames[i] );
|
|
break;
|
|
}
|
|
}
|
|
|
|
if( i == numLayers ) lm->gl_specular_id = LOAD_TEXTURE_ARRAY( (const char **)texnames, 0 );
|
|
if( !lm->gl_specular_id ) lm->gl_specular_id = tr.blackTexture;
|
|
|
|
return true;
|
|
}
|
|
|
|
/*
|
|
========================
|
|
R_FreeLandscapes
|
|
|
|
free the landscape definitions
|
|
========================
|
|
*/
|
|
void R_FreeLandscapes( void )
|
|
{
|
|
for( int i = 0; i < world->num_terrains; i++ )
|
|
{
|
|
terrain_t *terra = &world->terrains[i];
|
|
indexMap_t *im = &terra->indexmap;
|
|
if( im->pixels ) Mem_Free( im->pixels );
|
|
layerMap_t *lm = &terra->layermap;
|
|
|
|
if( lm->gl_diffuse_id )
|
|
FREE_TEXTURE( lm->gl_diffuse_id );
|
|
|
|
if( lm->gl_normalmap_id != tr.normalmapTexture )
|
|
FREE_TEXTURE( lm->gl_normalmap_id );
|
|
|
|
if( lm->gl_specular_id != tr.blackTexture )
|
|
FREE_TEXTURE( lm->gl_specular_id );
|
|
|
|
if( im->gl_diffuse_id != 0 )
|
|
FREE_TEXTURE( im->gl_diffuse_id );
|
|
|
|
FREE_TEXTURE( im->gl_heightmap_id );
|
|
}
|
|
|
|
if( world->terrains ) Mem_Free( world->terrains );
|
|
|
|
world->num_terrains = 0;
|
|
world->terrains = NULL;
|
|
}
|
|
|
|
/*
|
|
========================
|
|
R_LoadLandscapes
|
|
|
|
load the landscape definitions
|
|
========================
|
|
*/
|
|
void R_LoadLandscapes( const char *filename )
|
|
{
|
|
char filepath[256];
|
|
|
|
Q_snprintf( filepath, sizeof( filepath ), "maps/%s_land.txt", filename );
|
|
|
|
char *afile = (char *)gEngfuncs.COM_LoadFile( filepath, 5, NULL );
|
|
if( !afile ) return;
|
|
|
|
ALERT( at_aiconsole, "loading %s\n", filepath );
|
|
|
|
char *pfile = afile;
|
|
char token[256];
|
|
int depth = 0;
|
|
|
|
// count materials
|
|
while( pfile != NULL )
|
|
{
|
|
pfile = COM_ParseFile( pfile, token );
|
|
if( !pfile ) break;
|
|
|
|
if( Q_strlen( token ) > 1 )
|
|
continue;
|
|
|
|
if( token[0] == '{' )
|
|
{
|
|
depth++;
|
|
}
|
|
else if( token[0] == '}' )
|
|
{
|
|
world->num_terrains++;
|
|
depth--;
|
|
}
|
|
}
|
|
|
|
if( depth > 0 ) ALERT( at_warning, "%s: EOF reached without closing brace\n", filepath );
|
|
if( depth < 0 ) ALERT( at_warning, "%s: EOF reached without opening brace\n", filepath );
|
|
|
|
world->terrains = (terrain_t *)Mem_Alloc( sizeof( terrain_t ) * world->num_terrains );
|
|
pfile = afile; // start real parsing
|
|
|
|
int current = 0;
|
|
|
|
while( pfile != NULL )
|
|
{
|
|
pfile = COM_ParseFile( pfile, token );
|
|
if( !pfile ) break;
|
|
|
|
if( current >= world->num_terrains )
|
|
{
|
|
ALERT ( at_error, "landscape parse is overrun %d > %d\n", current, world->num_terrains );
|
|
break;
|
|
}
|
|
|
|
terrain_t *terra = &world->terrains[current];
|
|
|
|
// read the landscape name
|
|
Q_strncpy( terra->name, token, sizeof( terra->name ));
|
|
terra->texScale = 1.0f;
|
|
|
|
// read opening brace
|
|
pfile = COM_ParseFile( pfile, token );
|
|
if( !pfile ) break;
|
|
|
|
if( token[0] != '{' )
|
|
{
|
|
ALERT( at_error, "found %s when expecting {\n", token );
|
|
break;
|
|
}
|
|
|
|
while( pfile != NULL )
|
|
{
|
|
pfile = COM_ParseFile( pfile, token );
|
|
if( !pfile )
|
|
{
|
|
ALERT( at_error, "EOF without closing brace\n" );
|
|
goto land_getout;
|
|
}
|
|
|
|
// description end goto next material
|
|
if( token[0] == '}' )
|
|
{
|
|
current++;
|
|
break;
|
|
}
|
|
else if( !Q_stricmp( token, "indexMap" ))
|
|
{
|
|
pfile = COM_ParseFile( pfile, token );
|
|
if( !pfile )
|
|
{
|
|
ALERT( at_error, "hit EOF while parsing 'indexMap'\n" );
|
|
goto land_getout;
|
|
}
|
|
|
|
Q_strncpy( terra->indexmap.name, token, sizeof( terra->indexmap.name ));
|
|
}
|
|
else if( !Q_stricmp( token, "diffuseMap" ))
|
|
{
|
|
pfile = COM_ParseFile( pfile, token );
|
|
if( !pfile )
|
|
{
|
|
ALERT( at_error, "hit EOF while parsing 'diffuseMap'\n" );
|
|
goto land_getout;
|
|
}
|
|
|
|
Q_strncpy( terra->indexmap.diffuse, token, sizeof( terra->indexmap.diffuse ));
|
|
}
|
|
else if( !Q_strnicmp( token, "layer", 5 ))
|
|
{
|
|
int layerNum = Q_atoi( token + 5 );
|
|
|
|
pfile = COM_ParseFile( pfile, token );
|
|
if( !pfile )
|
|
{
|
|
ALERT( at_error, "hit EOF while parsing 'layer'\n" );
|
|
goto land_getout;
|
|
}
|
|
|
|
if( layerNum < 0 || layerNum > ( MAX_LANDSCAPE_LAYERS - 1 ))
|
|
{
|
|
ALERT( at_error, "%s is out of range. Ignored\n", token );
|
|
}
|
|
else
|
|
{
|
|
Q_strncpy( terra->layermap.pathes[layerNum], token, sizeof( terra->layermap.pathes[0] ));
|
|
COM_FileBase( terra->layermap.pathes[layerNum], terra->layermap.names[layerNum] );
|
|
}
|
|
|
|
terra->numLayers = Q_max( terra->numLayers, layerNum + 1 );
|
|
}
|
|
else if( !Q_stricmp( token, "tessSize" ))
|
|
{
|
|
pfile = COM_ParseFile( pfile, token );
|
|
if( !pfile )
|
|
{
|
|
ALERT( at_error, "hit EOF while parsing 'tessSize'\n" );
|
|
goto land_getout;
|
|
}
|
|
|
|
terra->tessSize = Q_atoi( token );
|
|
terra->tessSize = bound( 8, terra->tessSize, 256 );
|
|
}
|
|
else if( !Q_stricmp( token, "texScale" ))
|
|
{
|
|
pfile = COM_ParseFile( pfile, token );
|
|
if( !pfile )
|
|
{
|
|
ALERT( at_error, "hit EOF while parsing 'texScale'\n" );
|
|
goto land_getout;
|
|
}
|
|
|
|
terra->texScale = Q_atof( token );
|
|
terra->texScale = 1.0 / (bound( 0.000001f, terra->texScale, 16.0f ));
|
|
}
|
|
else ALERT( at_warning, "Unknown landscape token %s\n", token );
|
|
}
|
|
|
|
if( LoadHeightMap( &terra->indexmap, terra->numLayers ))
|
|
{
|
|
if( LoadTerrainLayers( &terra->layermap, terra->numLayers ))
|
|
terra->valid = true; // all done
|
|
}
|
|
}
|
|
|
|
land_getout:
|
|
gEngfuncs.COM_FreeFile( afile );
|
|
ALERT( at_console, "%d landscapes parsed\n", current );
|
|
}
|
|
|
|
/*
|
|
========================
|
|
R_FindTerrain
|
|
|
|
find the terrain description
|
|
========================
|
|
*/
|
|
terrain_t *R_FindTerrain( const char *texname )
|
|
{
|
|
for( int i = 0; i < world->num_terrains; i++ )
|
|
{
|
|
if( !Q_stricmp( texname, world->terrains[i].name ) && world->terrains[i].valid )
|
|
return &world->terrains[i];
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
/*
|
|
==================
|
|
R_InitShadowTextures
|
|
|
|
Re-create shadow textures
|
|
if them size was changed
|
|
==================
|
|
*/
|
|
void R_InitShadowTextures( void )
|
|
{
|
|
int i;
|
|
|
|
for( i = 0; i < MAX_SHADOWS; i++ )
|
|
{
|
|
if( !tr.shadowTextures[i] ) break;
|
|
FREE_TEXTURE( tr.shadowTextures[i] );
|
|
}
|
|
|
|
for( i = 0; i < MAX_SHADOWS; i++ )
|
|
{
|
|
if( !tr.shadowCubemaps[i] ) break;
|
|
FREE_TEXTURE( tr.shadowCubemaps[i] );
|
|
}
|
|
|
|
memset( tr.shadowTextures, 0, sizeof( tr.shadowTextures ));
|
|
memset( tr.shadowCubemaps, 0, sizeof( tr.shadowCubemaps ));
|
|
|
|
int base_shadow_size = bound( 128, r_shadowmap_size->value, 2048 );
|
|
|
|
int shadow_size2D = bound( 128, NearestPOW( base_shadow_size, true ), 2048 );
|
|
tr.fbo_shadow2D.Init( FBO_DEPTH, shadow_size2D, shadow_size2D, FBO_NOTEXTURE );
|
|
|
|
int shadow_sizeCM = bound( 128, NearestPOW( base_shadow_size * 0.5f, true ), 1024 );
|
|
tr.fbo_shadowCM.Init( FBO_DEPTH, shadow_sizeCM, shadow_sizeCM, FBO_NOTEXTURE );
|
|
}
|
|
|
|
/*
|
|
==================
|
|
R_InitCommonTextures
|
|
|
|
Init common textures for renderer that size
|
|
based on screen size
|
|
==================
|
|
*/
|
|
void R_InitCommonTextures( void )
|
|
{
|
|
int i;
|
|
|
|
GL_BindFBO( 0 );
|
|
R_InitShadowTextures();
|
|
|
|
// release old subview textures
|
|
for( i = 0; i < MAX_SUBVIEW_TEXTURES; i++ )
|
|
{
|
|
if( !tr.subviewTextures[i].texturenum ) break;
|
|
FREE_TEXTURE( tr.subviewTextures[i].texturenum );
|
|
}
|
|
|
|
memset( tr.subviewTextures, 0, sizeof( tr.subviewTextures ));
|
|
|
|
for( i = 0; i < tr.num_framebuffers; i++ )
|
|
{
|
|
if( !tr.frame_buffers[i].init )
|
|
break;
|
|
R_FreeFrameBuffer( i );
|
|
}
|
|
|
|
for( i = 0; i < MAX_SHADOWMAPS; i++ )
|
|
{
|
|
tr.sunShadowFBO[i].Init( FBO_DEPTH, sunSize[i], sunSize[i] );
|
|
}
|
|
|
|
tr.fbo_light.Init( FBO_COLOR, glState.width, glState.height, FBO_LINEAR );
|
|
tr.fbo_filter.Init( FBO_COLOR, glState.width, glState.height, FBO_LINEAR );
|
|
tr.fbo_shadow.Init( FBO_COLOR, glState.defWidth, glState.defHeight, FBO_LINEAR );
|
|
|
|
// setup the skybox sides
|
|
for( i = 0; i < 6; i++ )
|
|
tr.skyboxTextures[i] = RENDER_GET_PARM( PARM_TEX_SKYBOX, i );
|
|
|
|
tr.num_framebuffers = 0;
|
|
tr.num_subview_used = 0;
|
|
tr.glsl_valid_sequence++; // refresh shader cache
|
|
|
|
InitPostTextures();
|
|
}
|
|
|
|
/*
|
|
====================
|
|
ED_ParseEdict
|
|
|
|
Parses an edict out of the given string, returning the new position
|
|
ed should be a properly initialized empty edict.
|
|
====================
|
|
*/
|
|
void ED_ParseEdict( char **pfile )
|
|
{
|
|
int vertex_light_cache = -1;
|
|
int surface_light_cache = -1;
|
|
char modelname[64];
|
|
char token[2048];
|
|
|
|
// go through all the dictionary pairs
|
|
while( 1 )
|
|
{
|
|
char keyname[256];
|
|
|
|
// parse key
|
|
if(( *pfile = COM_ParseFile( *pfile, token )) == NULL )
|
|
HOST_ERROR( "ED_ParseEdict: EOF without closing brace\n" );
|
|
|
|
if( token[0] == '}' ) break; // end of desc
|
|
|
|
Q_strncpy( keyname, token, sizeof( keyname ));
|
|
|
|
// parse value
|
|
if(( *pfile = COM_ParseFile( *pfile, token )) == NULL )
|
|
HOST_ERROR( "ED_ParseEdict: EOF without closing brace\n" );
|
|
|
|
if( token[0] == '}' )
|
|
HOST_ERROR( "ED_ParseEdict: closing brace without data\n" );
|
|
|
|
// ignore attempts to set key ""
|
|
if( !keyname[0] ) continue;
|
|
|
|
// "wad" field is completely ignored in XashXT
|
|
if( !Q_strcmp( keyname, "wad" ))
|
|
continue;
|
|
|
|
// ignore attempts to set value ""
|
|
if( !token[0] ) continue;
|
|
|
|
// also grab light settings from p2rad
|
|
if( !Q_strcmp( keyname, "_lightgamma" ))
|
|
tr.light_gamma = Q_atof( token );
|
|
if( !Q_strcmp( keyname, "_dscale" ))
|
|
tr.direct_scale = Q_atof( token );
|
|
if( !Q_strcmp( keyname, "_maxlight" ))
|
|
tr.light_threshold = Q_atof( token );
|
|
if( !Q_strcmp( keyname, "_ambient" ))
|
|
Q_atov( tr.ambient_color, token, 3 );
|
|
if( !Q_strcmp( keyname, "_smooth" ))
|
|
tr.smoothing_threshold = cos( DEG2RAD( Q_atof( token )));
|
|
|
|
// only two fields that we needed
|
|
if( !Q_strcmp( keyname, "model" ))
|
|
Q_strncpy( modelname, token, sizeof( modelname ));
|
|
|
|
if( !Q_strcmp( keyname, "vlight_cache" ))
|
|
vertex_light_cache = atoi( token );
|
|
if( !Q_strcmp( keyname, "flight_cache" ))
|
|
surface_light_cache = atoi( token );
|
|
}
|
|
|
|
// deal with light cache
|
|
double start = Sys_DoubleTime();
|
|
if( vertex_light_cache > 0 && vertex_light_cache < MAX_LIGHTCACHE )
|
|
g_StudioRenderer.CreateStudioCacheVL( modelname, vertex_light_cache - 1 );
|
|
if( surface_light_cache > 0 && surface_light_cache < MAX_LIGHTCACHE )
|
|
g_StudioRenderer.CreateStudioCacheFL( modelname, surface_light_cache - 1 );
|
|
double end = Sys_DoubleTime();
|
|
|
|
r_buildstats.create_light_cache += (end - start);
|
|
r_buildstats.total_buildtime += (end - start);
|
|
}
|
|
|
|
/*
|
|
==================
|
|
GL_InitModelLightCache
|
|
|
|
create VBO cache for vertex-lit and lightmapped studio models
|
|
==================
|
|
*/
|
|
void GL_InitModelLightCache( void )
|
|
{
|
|
char *entities = worldmodel->entities;
|
|
static char worldname[64];
|
|
char token[2048];
|
|
|
|
if( !Q_stricmp( world->name, worldname ))
|
|
return; // just a restart
|
|
|
|
Q_strncpy( worldname, world->name, sizeof( worldname ));
|
|
RI->currententity = GET_ENTITY( 0 ); // to have something valid here
|
|
|
|
// parse ents to find vertex light cache
|
|
while(( entities = COM_ParseFile( entities, token )) != NULL )
|
|
{
|
|
if( token[0] != '{' )
|
|
HOST_ERROR( "ED_LoadFromFile: found %s when expecting {\n", token );
|
|
|
|
ED_ParseEdict( &entities );
|
|
}
|
|
|
|
// create lightmap pages right after studiomodel alloc their lightmaps (empty at this moment)
|
|
GL_EndBuildingLightmaps( (worldmodel->lightdata != NULL), FBitSet( world->features, WORLD_HAS_DELUXEMAP ) ? true : false );
|
|
}
|
|
|
|
/*
|
|
==================
|
|
R_NewMap
|
|
|
|
Called always when map is changed or restarted
|
|
==================
|
|
*/
|
|
void R_NewMap( void )
|
|
{
|
|
// setup special flags
|
|
for( int i = 0; i < worldmodel->numsurfaces; i++ )
|
|
{
|
|
msurface_t *surf = &worldmodel->surfaces[i];
|
|
mextrasurf_t *info = surf->info;
|
|
|
|
// clear the in-game set flags
|
|
ClearBits( surf->flags, SURF_NODRAW|SURF_NODLIGHT|SURF_NOSUNLIGHT|SURF_REFLECT_PUDDLE );
|
|
|
|
// clear occlusion queries results
|
|
ClearBits( surf->flags, SURF_QUEUED|SURF_OCCLUDED );
|
|
|
|
memset( info->subtexture, 0, sizeof( info->subtexture ));
|
|
info->cubemap[0] = &world->defaultCubemap;
|
|
info->cubemap[1] = &world->defaultCubemap;
|
|
info->checkcount = -1;
|
|
}
|
|
|
|
// we need to reapply cubemaps to surfaces after restart level
|
|
if( world->num_cubemaps > 0 )
|
|
world->loading_cubemaps = true;
|
|
|
|
// reset sky settings
|
|
tr.sky_origin = tr.sky_world_origin = g_vecZero;
|
|
tr.sky_speed = 0;
|
|
|
|
// reset light settings
|
|
tr.light_gamma = 0.5;
|
|
tr.direct_scale = 2.0;
|
|
tr.light_threshold = 196;
|
|
tr.ambient_color = g_vecZero;
|
|
tr.smoothing_threshold = 0.642788f; // 50 degrees
|
|
}
|
|
|
|
/*
|
|
==================
|
|
R_InitDynLightShader
|
|
|
|
changed dynamic lighting shaders
|
|
==================
|
|
*/
|
|
void R_InitDynLightShader( int type )
|
|
{
|
|
char options[MAX_OPTIONS_LENGTH];
|
|
|
|
switch( type )
|
|
{
|
|
case LIGHT_SPOT:
|
|
GL_SetShaderDirective( options, "LIGHT_SPOT" );
|
|
break;
|
|
case LIGHT_OMNI:
|
|
GL_SetShaderDirective( options, "LIGHT_OMNI" );
|
|
break;
|
|
default: return;
|
|
break;
|
|
}
|
|
|
|
if( CVAR_TO_BOOL( cv_brdf ))
|
|
GL_AddShaderDirective( options, "APPLY_PBS" );
|
|
|
|
if( CVAR_TO_BOOL( cv_specular ))
|
|
GL_AddShaderDirective( options, "HAS_GLOSSMAP" );
|
|
|
|
if( CVAR_TO_BOOL( r_shadows ))
|
|
{
|
|
// shadow cubemaps only support if GL_EXT_gpu_shader4 is support
|
|
if( type == LIGHT_SPOT || GL_Support( R_EXT_GPU_SHADER4 ))
|
|
{
|
|
GL_AddShaderDirective( options, "APPLY_SHADOW" );
|
|
|
|
if( r_shadows->value == 2.0f )
|
|
GL_AddShaderDirective( options, "SHADOW_PCF2X2" );
|
|
else if( r_shadows->value >= 3.0f )
|
|
GL_AddShaderDirective( options, "SHADOW_PCF3X3" );
|
|
}
|
|
}
|
|
|
|
tr.defDynLightShader[type] = GL_FindShader( "deferred/dynlight", "deferred/generic", "deferred/dynlight", options );
|
|
}
|
|
|
|
/*
|
|
==================
|
|
R_InitDynLightShaders
|
|
|
|
changed dynamic lighting shaders
|
|
==================
|
|
*/
|
|
void R_InitDynLightShaders( void )
|
|
{
|
|
char options[MAX_OPTIONS_LENGTH];
|
|
|
|
GL_CleanupDrawState();
|
|
|
|
R_InitDynLightShader( LIGHT_SPOT );
|
|
R_InitDynLightShader( LIGHT_OMNI );
|
|
|
|
options[0] = '\0';
|
|
|
|
if( CVAR_TO_BOOL( cv_deferred_tracebmodels ))
|
|
GL_AddShaderDirective( options, "BSPTRACE_BMODELS" );
|
|
|
|
// init deferred shaders
|
|
tr.defSceneShader[0] = GL_FindShader( "deferred/scene_sep", "deferred/generic", "deferred/scene_sep", options );
|
|
tr.defSceneShader[1] = GL_FindShader( "deferred/scene_all", "deferred/generic", "deferred/scene_all", options );
|
|
tr.defLightShader = GL_FindShader( "deferred/light", "deferred/generic", "deferred/light", options );
|
|
tr.bilateralShader = GL_FindUberShader( "deferred/bilateral" );
|
|
}
|
|
|
|
/*
|
|
==================
|
|
R_VidInit
|
|
|
|
Called always when "vid_mode" or "fullscreen" cvars is changed
|
|
==================
|
|
*/
|
|
void R_VidInit( void )
|
|
{
|
|
// get the actual screen size
|
|
glState.width = RENDER_GET_PARM( PARM_SCREEN_WIDTH, 0 );
|
|
glState.height = RENDER_GET_PARM( PARM_SCREEN_HEIGHT, 0 );
|
|
|
|
// TESTTEST
|
|
glState.defWidth = 256;
|
|
glState.defHeight = 192;
|
|
|
|
R_InitCommonTextures();
|
|
GL_VidInitDrawBuffers();
|
|
}
|
|
|
|
/*
|
|
==================
|
|
GL_MapChanged
|
|
|
|
Called always when map is changed or restarted
|
|
==================
|
|
*/
|
|
void GL_MapChanged( void )
|
|
{
|
|
if( !g_fRenderInitialized )
|
|
return;
|
|
|
|
R_InitRefState();
|
|
|
|
tr.glsl_valid_sequence = 1;
|
|
tr.grassunloadframe = 0;
|
|
tr.fClearScreen = false;
|
|
tr.realframecount = 1;
|
|
tr.num_cin_used = 0;
|
|
|
|
// catch changes of screen
|
|
R_VidInit ();
|
|
|
|
g_pParticleSystems.ClearSystems(); // buz
|
|
|
|
g_pParticles.Clear();
|
|
|
|
CL_ClearDlights();
|
|
|
|
ClearDecals();
|
|
|
|
ResetRain();
|
|
|
|
// don't flush shaders for each map - save time to recompile
|
|
if( num_glsl_programs >= ( MAX_GLSL_PROGRAMS * 0.9f ))
|
|
GL_FreeUberShaders();
|
|
|
|
R_NewMap (); // tell the renderer what a new map started
|
|
|
|
g_StudioRenderer.VidInit();
|
|
|
|
GL_InitModelLightCache();
|
|
} |