530 lines
13 KiB
C++
530 lines
13 KiB
C++
|
#include "../server/exe_headers.h"
|
||
|
|
||
|
#include "../game/q_shared.h"
|
||
|
|
||
|
#include "cm_local.h"
|
||
|
#include "memory.h"
|
||
|
#include "chash.h"
|
||
|
|
||
|
class CCMShaderText
|
||
|
{
|
||
|
private:
|
||
|
char mName[MAX_QPATH];
|
||
|
class CCMShaderText *mNext;
|
||
|
const char *mData;
|
||
|
public:
|
||
|
// Constructors
|
||
|
CCMShaderText(const char *name, const char *data) { Q_strncpyz(mName, name, MAX_QPATH); mNext = NULL; mData = data; }
|
||
|
~CCMShaderText(void) {}
|
||
|
|
||
|
// Accessors
|
||
|
const char *GetName(void) const { return(mName); }
|
||
|
class CCMShaderText *GetNext(void) const { return(mNext); }
|
||
|
void SetNext(class CCMShaderText *next) { mNext = next; }
|
||
|
void Destroy(void) { delete this; }
|
||
|
|
||
|
const char *GetData(void) const { return(mData); }
|
||
|
};
|
||
|
|
||
|
char *shaderText = NULL;
|
||
|
CHash<CCMShaderText> shaderTextTable;
|
||
|
CHash<CCMShader> cmShaderTable;
|
||
|
|
||
|
const char *SkipWhitespace( const char *data, qboolean *hasNewLines );
|
||
|
|
||
|
//rwwFIXMEFIXME: Called at RE_BeginRegistration because Hunk_Clear
|
||
|
//destroys the memory cmShaderTable is on. This is a temp solution
|
||
|
//I guess.
|
||
|
void ShaderTableCleanup()
|
||
|
{
|
||
|
cmShaderTable.clear();
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
====================
|
||
|
CM_CreateShaderTextHash
|
||
|
=====================
|
||
|
*/
|
||
|
void CM_CreateShaderTextHash(void)
|
||
|
{
|
||
|
const char *p;
|
||
|
qboolean hasNewLines;
|
||
|
char *token;
|
||
|
CCMShaderText *shader;
|
||
|
|
||
|
p = shaderText;
|
||
|
// look for label
|
||
|
while (p)
|
||
|
{
|
||
|
p = SkipWhitespace(p, &hasNewLines);
|
||
|
token = COM_ParseExt( &p, qtrue );
|
||
|
if ( !token[0] )
|
||
|
{
|
||
|
break;
|
||
|
}
|
||
|
shader = new CCMShaderText(token, p);
|
||
|
shaderTextTable.insert(shader);
|
||
|
|
||
|
SkipBracedSection(&p);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
====================
|
||
|
CM_LoadShaderFiles
|
||
|
|
||
|
Finds and loads all .shader files, combining them into
|
||
|
a single large text block that can be scanned for shader names
|
||
|
=====================
|
||
|
*/
|
||
|
#define MAX_SHADER_FILES 1024
|
||
|
|
||
|
void CM_LoadShaderFiles( void )
|
||
|
{
|
||
|
char **shaderFiles1;
|
||
|
int numShaders1;
|
||
|
char *buffers[MAX_SHADER_FILES];
|
||
|
int numShaders;
|
||
|
int i;
|
||
|
int sum = 0;
|
||
|
|
||
|
// scan for shader files
|
||
|
shaderFiles1 = FS_ListFiles( "shaders", ".shader", &numShaders1 );
|
||
|
|
||
|
if ( !shaderFiles1 || !numShaders1 )
|
||
|
{
|
||
|
Com_Printf( S_COLOR_YELLOW "WARNING: no shader files found\n" );
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
numShaders = numShaders1;
|
||
|
if ( numShaders > MAX_SHADER_FILES )
|
||
|
{
|
||
|
numShaders = MAX_SHADER_FILES;
|
||
|
}
|
||
|
|
||
|
// load and parse shader files
|
||
|
for ( i = 0; i < numShaders1; i++ )
|
||
|
{
|
||
|
char filename[MAX_QPATH];
|
||
|
|
||
|
Com_sprintf( filename, sizeof( filename ), "shaders/%s", shaderFiles1[i] );
|
||
|
Com_DPrintf( "...loading '%s'\n", filename );
|
||
|
FS_ReadFile( filename, (void **)&buffers[i] );
|
||
|
if ( !buffers[i] )
|
||
|
{
|
||
|
Com_Error( ERR_DROP, "Couldn't load %s", filename );
|
||
|
}
|
||
|
sum += COM_Compress( buffers[i] );
|
||
|
}
|
||
|
|
||
|
// build single large buffer
|
||
|
shaderText = (char *)Z_Malloc( sum + numShaders * 2, TAG_SHADERTEXT, qtrue);
|
||
|
|
||
|
// free in reverse order, so the temp files are all dumped
|
||
|
for ( i = numShaders - 1; i >= 0 ; i-- )
|
||
|
{
|
||
|
strcat( shaderText, "\n" );
|
||
|
strcat( shaderText, buffers[i] );
|
||
|
FS_FreeFile( buffers[i] );
|
||
|
}
|
||
|
|
||
|
// free up memory
|
||
|
FS_FreeFileList( shaderFiles1 );
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
==================
|
||
|
CM_GetShaderText
|
||
|
==================
|
||
|
*/
|
||
|
|
||
|
const char *CM_GetShaderText(const char *key)
|
||
|
{
|
||
|
CCMShaderText *st;
|
||
|
|
||
|
st = shaderTextTable[key];
|
||
|
if(st)
|
||
|
{
|
||
|
return(st->GetData());
|
||
|
}
|
||
|
return(NULL);
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
==================
|
||
|
CM_FreeShaderText
|
||
|
==================
|
||
|
*/
|
||
|
|
||
|
void CM_FreeShaderText(void)
|
||
|
{
|
||
|
shaderTextTable.clear();
|
||
|
if(shaderText)
|
||
|
{
|
||
|
Z_Free(shaderText);
|
||
|
shaderText = NULL;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
==================
|
||
|
CM_LoadShaderText
|
||
|
|
||
|
Loads in all the .shader files so it can be accessed by the server and the renderer
|
||
|
Creates a hash table to quickly access the shader text
|
||
|
==================
|
||
|
*/
|
||
|
|
||
|
void CM_LoadShaderText(bool forceReload)
|
||
|
{
|
||
|
if(forceReload)
|
||
|
{
|
||
|
CM_FreeShaderText();
|
||
|
}
|
||
|
if(shaderText)
|
||
|
{
|
||
|
return;
|
||
|
}
|
||
|
Com_Printf("Loading shader text .....\n");
|
||
|
CM_LoadShaderFiles();
|
||
|
CM_CreateShaderTextHash();
|
||
|
|
||
|
Com_Printf("..... %d shader definitions loaded\n", shaderTextTable.count());
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
===============
|
||
|
ParseSurfaceParm
|
||
|
|
||
|
surfaceparm <name>
|
||
|
===============
|
||
|
*/
|
||
|
|
||
|
typedef struct
|
||
|
{
|
||
|
char *name;
|
||
|
int clearSolid, surfaceFlags, contents;
|
||
|
} infoParm_t;
|
||
|
|
||
|
infoParm_t svInfoParms[] =
|
||
|
{
|
||
|
// Game content Flags
|
||
|
{"nonsolid", ~CONTENTS_SOLID, 0, 0 }, // special hack to clear solid flag
|
||
|
{"nonopaque", ~CONTENTS_OPAQUE, 0, 0 }, // special hack to clear opaque flag
|
||
|
{"lava", ~CONTENTS_SOLID, 0, CONTENTS_LAVA }, // very damaging
|
||
|
{"slime", ~CONTENTS_SOLID, 0, CONTENTS_SLIME }, // mildly damaging
|
||
|
{"water", ~CONTENTS_SOLID, 0, CONTENTS_WATER },
|
||
|
{"fog", ~CONTENTS_SOLID, 0, CONTENTS_FOG}, // carves surfaces entering
|
||
|
{"shotclip", ~CONTENTS_SOLID, 0, CONTENTS_SHOTCLIP }, /* block shots, but not people */
|
||
|
{"playerclip", ~(CONTENTS_SOLID|CONTENTS_OPAQUE),0,CONTENTS_PLAYERCLIP }, /* block only the player */
|
||
|
{"monsterclip", ~(CONTENTS_SOLID|CONTENTS_OPAQUE),0,CONTENTS_MONSTERCLIP },
|
||
|
{"botclip", ~(CONTENTS_SOLID|CONTENTS_OPAQUE),0,CONTENTS_BOTCLIP }, /* NPC do not enter */
|
||
|
{"trigger", ~(CONTENTS_SOLID|CONTENTS_OPAQUE),0,CONTENTS_TRIGGER },
|
||
|
{"nodrop", ~(CONTENTS_SOLID|CONTENTS_OPAQUE),0,CONTENTS_NODROP }, // don't drop items or leave bodies (death fog, lava, etc)
|
||
|
{"terrain", ~(CONTENTS_SOLID|CONTENTS_OPAQUE),0,CONTENTS_TERRAIN }, /* use special terrain collsion */
|
||
|
{"ladder", ~(CONTENTS_SOLID|CONTENTS_OPAQUE),0,CONTENTS_LADDER }, // climb up in it like water
|
||
|
{"abseil", ~(CONTENTS_SOLID|CONTENTS_OPAQUE),0,CONTENTS_ABSEIL }, // can abseil down this brush
|
||
|
{"outside", ~(CONTENTS_SOLID|CONTENTS_OPAQUE),0,CONTENTS_OUTSIDE }, // volume is considered to be in the outside (i.e. not indoors)
|
||
|
{"inside", ~(CONTENTS_SOLID|CONTENTS_OPAQUE),0,CONTENTS_INSIDE }, // volume is considered to be inside (i.e. indoors)
|
||
|
|
||
|
{"detail", -1, 0, CONTENTS_DETAIL }, // don't include in structural bsp
|
||
|
{"trans", -1, 0, CONTENTS_TRANSLUCENT }, // surface has an alpha component
|
||
|
|
||
|
/* Game surface flags */
|
||
|
{"sky", -1, SURF_SKY, 0 }, /* emit light from an environment map */
|
||
|
{"slick", -1, SURF_SLICK, 0 },
|
||
|
|
||
|
{"nodamage", -1, SURF_NODAMAGE, 0 },
|
||
|
{"noimpact", -1, SURF_NOIMPACT, 0 }, /* don't make impact explosions or marks */
|
||
|
{"nomarks", -1, SURF_NOMARKS, 0 }, /* don't make impact marks, but still explode */
|
||
|
{"nodraw", -1, SURF_NODRAW, 0 }, /* don't generate a drawsurface (or a lightmap) */
|
||
|
{"nosteps", -1, SURF_NOSTEPS, 0 },
|
||
|
{"nodlight", -1, SURF_NODLIGHT, 0 }, /* don't ever add dynamic lights */
|
||
|
{"metalsteps", -1, SURF_METALSTEPS,0 },
|
||
|
{"nomiscents", -1, SURF_NOMISCENTS,0 }, /* No misc ents on this surface */
|
||
|
{"forcefield", -1, SURF_FORCEFIELD,0 },
|
||
|
{"forcesight", -1, SURF_FORCESIGHT,0 }, // only visible with force sight
|
||
|
};
|
||
|
|
||
|
void SV_ParseSurfaceParm( CCMShader * shader, const char **text )
|
||
|
{
|
||
|
char *token;
|
||
|
int numsvInfoParms = sizeof(svInfoParms) / sizeof(svInfoParms[0]);
|
||
|
int i;
|
||
|
|
||
|
token = COM_ParseExt( text, qfalse );
|
||
|
for ( i = 0 ; i < numsvInfoParms ; i++ )
|
||
|
{
|
||
|
if ( !Q_stricmp( token, svInfoParms[i].name ) )
|
||
|
{
|
||
|
shader->surfaceFlags |= svInfoParms[i].surfaceFlags;
|
||
|
shader->contentFlags |= svInfoParms[i].contents;
|
||
|
shader->contentFlags &= svInfoParms[i].clearSolid;
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
=================
|
||
|
ParseMaterial
|
||
|
=================
|
||
|
*/
|
||
|
const char *svMaterialNames[MATERIAL_LAST] =
|
||
|
{
|
||
|
MATERIALS
|
||
|
};
|
||
|
|
||
|
void SV_ParseMaterial( CCMShader *shader, const char **text )
|
||
|
{
|
||
|
char *token;
|
||
|
int i;
|
||
|
|
||
|
token = COM_ParseExt( text, qfalse );
|
||
|
if ( !token[0] )
|
||
|
{
|
||
|
Com_Printf( S_COLOR_YELLOW "WARNING: missing material in shader '%s'\n", shader->shader );
|
||
|
return;
|
||
|
}
|
||
|
for(i = 0; i < MATERIAL_LAST; i++)
|
||
|
{
|
||
|
if ( !Q_stricmp( token, svMaterialNames[i] ) )
|
||
|
{
|
||
|
shader->surfaceFlags &= ~MATERIAL_MASK;//safety, clear it first
|
||
|
shader->surfaceFlags |= i;
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
===============
|
||
|
ParseVector
|
||
|
===============
|
||
|
*/
|
||
|
qboolean CM_ParseVector( CCMShader *shader, const char **text, int count, float *v )
|
||
|
{
|
||
|
char *token;
|
||
|
int i;
|
||
|
|
||
|
// FIXME: spaces are currently required after parens, should change parseext...
|
||
|
token = COM_ParseExt( text, qfalse );
|
||
|
if ( strcmp( token, "(" ) )
|
||
|
{
|
||
|
Com_Printf( S_COLOR_YELLOW "WARNING: missing parenthesis in shader '%s'\n", shader->shader );
|
||
|
return qfalse;
|
||
|
}
|
||
|
|
||
|
for ( i = 0 ; i < count ; i++ )
|
||
|
{
|
||
|
token = COM_ParseExt( text, qfalse );
|
||
|
if ( !token[0] )
|
||
|
{
|
||
|
Com_Printf( S_COLOR_YELLOW "WARNING: missing vector element in shader '%s'\n", shader->shader );
|
||
|
return qfalse;
|
||
|
}
|
||
|
v[i] = atof( token );
|
||
|
}
|
||
|
|
||
|
token = COM_ParseExt( text, qfalse );
|
||
|
if ( strcmp( token, ")" ) )
|
||
|
{
|
||
|
Com_Printf( S_COLOR_YELLOW "WARNING: missing parenthesis in shader '%s'\n", shader->shader );
|
||
|
return qfalse;
|
||
|
}
|
||
|
return qtrue;
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
=================
|
||
|
CM_ParseShader
|
||
|
|
||
|
The current text pointer is at the explicit text definition of the
|
||
|
shader. Parse it into the global shader variable.
|
||
|
|
||
|
This extracts all the info from the shader required for physics and collision
|
||
|
It is designed to *NOT* load any image files and not require any of the renderer to
|
||
|
be initialised.
|
||
|
=================
|
||
|
*/
|
||
|
void CM_ParseShader( CCMShader *shader, const char **text )
|
||
|
{
|
||
|
char *token;
|
||
|
|
||
|
token = COM_ParseExt( text, qtrue );
|
||
|
if ( token[0] != '{' )
|
||
|
{
|
||
|
Com_Printf( S_COLOR_YELLOW "WARNING: expecting '{', found '%s' instead in shader '%s'\n", token, shader->shader );
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
while ( true )
|
||
|
{
|
||
|
token = COM_ParseExt( text, qtrue );
|
||
|
if ( !token[0] )
|
||
|
{
|
||
|
Com_Printf( S_COLOR_YELLOW "WARNING: no concluding '}' in shader %s\n", shader->shader );
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
// end of shader definition
|
||
|
if ( token[0] == '}' )
|
||
|
{
|
||
|
break;
|
||
|
}
|
||
|
// stage definition
|
||
|
else if ( token[0] == '{' )
|
||
|
{
|
||
|
SkipBracedSection( text );
|
||
|
continue;
|
||
|
}
|
||
|
// material deprecated as of 11 Jan 01
|
||
|
// material undeprecated as of 7 May 01 - q3map_material deprecated
|
||
|
else if ( !Q_stricmp( token, "material" ) || !Q_stricmp( token, "q3map_material" ) )
|
||
|
{
|
||
|
SV_ParseMaterial( shader, text );
|
||
|
}
|
||
|
// sun parms
|
||
|
// q3map_sun deprecated as of 11 Jan 01
|
||
|
else if ( !Q_stricmp( token, "sun" ) || !Q_stricmp( token, "q3map_sun" ) )
|
||
|
{
|
||
|
// float a, b;
|
||
|
|
||
|
token = COM_ParseExt( text, qfalse );
|
||
|
// shader->sunLight[0] = atof( token );
|
||
|
token = COM_ParseExt( text, qfalse );
|
||
|
// shader->sunLight[1] = atof( token );
|
||
|
token = COM_ParseExt( text, qfalse );
|
||
|
// shader->sunLight[2] = atof( token );
|
||
|
|
||
|
// VectorNormalize( shader->sunLight );
|
||
|
|
||
|
token = COM_ParseExt( text, qfalse );
|
||
|
// a = atof( token );
|
||
|
// VectorScale( shader->sunLight, a, shader->sunLight);
|
||
|
|
||
|
token = COM_ParseExt( text, qfalse );
|
||
|
// a = DEG2RAD(atof( token ));
|
||
|
|
||
|
token = COM_ParseExt( text, qfalse );
|
||
|
// b = DEG2RAD(atof( token ));
|
||
|
|
||
|
// shader->sunDirection[0] = cos( a ) * cos( b );
|
||
|
// shader->sunDirection[1] = sin( a ) * cos( b );
|
||
|
// shader->sunDirection[2] = sin( b );
|
||
|
}
|
||
|
else if ( !Q_stricmp( token, "surfaceParm" ) )
|
||
|
{
|
||
|
SV_ParseSurfaceParm( shader, text );
|
||
|
continue;
|
||
|
}
|
||
|
else if ( !Q_stricmp( token, "fogParms" ) )
|
||
|
{
|
||
|
vec3_t fogColor;
|
||
|
if ( !CM_ParseVector( shader, text, 3, fogColor ) )
|
||
|
{
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
token = COM_ParseExt( text, qfalse );
|
||
|
if ( !token[0] )
|
||
|
{
|
||
|
Com_Printf( S_COLOR_YELLOW "WARNING: missing parm for 'fogParms' keyword in shader '%s'\n", shader->shader );
|
||
|
continue;
|
||
|
}
|
||
|
// shader->depthForOpaque = atof( token );
|
||
|
|
||
|
// skip any old gradient directions
|
||
|
SkipRestOfLine( (const char **)text );
|
||
|
continue;
|
||
|
}
|
||
|
}
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
=================
|
||
|
CM_SetupShaderProperties
|
||
|
|
||
|
Scans thru the shaders loaded for the map, parses the text of that shader and
|
||
|
extracts the interesting info *WITHOUT* loading up any images or requiring
|
||
|
the renderer to be active.
|
||
|
=================
|
||
|
*/
|
||
|
|
||
|
void CM_SetupShaderProperties(void)
|
||
|
{
|
||
|
int i;
|
||
|
const char *def;
|
||
|
CCMShader *shader;
|
||
|
|
||
|
// Add all basic shaders to the cmShaderTable
|
||
|
for(i = 0; i < cmg.numShaders; i++)
|
||
|
{
|
||
|
cmShaderTable.insert(CM_GetShaderInfo(i));
|
||
|
}
|
||
|
// Go through and parse evaluate shader names to shadernums
|
||
|
for(i = 0; i < cmg.numShaders; i++)
|
||
|
{
|
||
|
shader = CM_GetShaderInfo(i);
|
||
|
def = CM_GetShaderText(shader->shader);
|
||
|
if(def)
|
||
|
{
|
||
|
CM_ParseShader(shader, &def);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
void CM_ShutdownShaderProperties(void)
|
||
|
{
|
||
|
if(cmShaderTable.count())
|
||
|
{
|
||
|
Com_Printf("Shutting down cmShaderTable .....\n");
|
||
|
cmShaderTable.clear();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
CCMShader *CM_GetShaderInfo( const char *name )
|
||
|
{
|
||
|
CCMShader *out;
|
||
|
const char *def;
|
||
|
|
||
|
out = cmShaderTable[name];
|
||
|
if(out)
|
||
|
{
|
||
|
return(out);
|
||
|
}
|
||
|
|
||
|
// Create a new CCMShader class
|
||
|
//out = (CCMShader *)Hunk_Alloc( sizeof( CCMShader ), h_high );
|
||
|
out = (CCMShader *)Hunk_Alloc( sizeof( CCMShader ), qtrue );
|
||
|
// Set defaults
|
||
|
Q_strncpyz(out->shader, name, MAX_QPATH);
|
||
|
out->contentFlags = CONTENTS_SOLID | CONTENTS_OPAQUE;
|
||
|
|
||
|
// Parse in any text if it exists
|
||
|
def = CM_GetShaderText(name);
|
||
|
if(def)
|
||
|
{
|
||
|
CM_ParseShader(out, &def);
|
||
|
}
|
||
|
|
||
|
cmShaderTable.insert(out);
|
||
|
return(out);
|
||
|
}
|
||
|
|
||
|
CCMShader *CM_GetShaderInfo( int shaderNum )
|
||
|
{
|
||
|
CCMShader *out;
|
||
|
|
||
|
if((shaderNum < 0) || (shaderNum >= cmg.numShaders))
|
||
|
{
|
||
|
return(NULL);
|
||
|
}
|
||
|
out = cmg.shaders + shaderNum;
|
||
|
return(out);
|
||
|
}
|
||
|
|
||
|
// end
|