jedioutcast/CODE-mp/qcommon/cm_shader.cpp
2013-04-04 13:24:26 -05:00

618 lines
15 KiB
C++

#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;
/*
====================
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;
#ifndef FINAL_BUILD
char **shaderFiles2;
int numShaders2;
#endif
char *buffers[MAX_SHADER_FILES];
int numShaders;
int i;
int sum = 0;
// scan for shader files
shaderFiles1 = FS_ListFiles( "shaders", ".shader", &numShaders1 );
#ifndef FINAL_BUILD
shaderFiles2 = FS_ListFiles( "shaders/test", ".shader", &numShaders2 );
#endif
if ( !shaderFiles1 || !numShaders1 )
{
Com_Printf( S_COLOR_YELLOW "WARNING: no shader files found\n" );
return;
}
#ifndef FINAL_BUILD
numShaders = numShaders1 + numShaders2;
#else
numShaders = numShaders1;
#endif
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 );
sum += FS_ReadFile( filename, (void **)&buffers[i] );
if ( !buffers[i] )
{
Com_Error( ERR_DROP, "Couldn't load %s", filename );
}
}
#ifndef FINAL_BUILD
for ( ; i < numShaders; i++ )
{
char filename[MAX_QPATH];
Com_sprintf( filename, sizeof( filename ), "shaders/test/%s", shaderFiles2[i - numShaders1] );
Com_DPrintf( "...loading '%s'\n", filename );
sum += FS_ReadFile( filename, (void **)&buffers[i] );
if ( !buffers[i] )
{
Com_Error( ERR_DROP, "Couldn't load %s", filename );
}
}
#endif
// 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 );
#ifndef FINAL_BUILD
FS_FreeFileList( shaderFiles2 );
#endif
}
/*
==================
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(qboolean 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 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
// 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
{"water", ~CONTENTS_SOLID, 0, CONTENTS_WATER },
{"fog", ~CONTENTS_SOLID, 0, CONTENTS_FOG}, // carves surfaces entering
{"playerclip", ~CONTENTS_SOLID, 0, CONTENTS_PLAYERCLIP },
{"monsterclip", ~CONTENTS_SOLID, 0, CONTENTS_MONSTERCLIP },
{"botclip", ~CONTENTS_SOLID, 0, CONTENTS_BOTCLIP }, // for bots
{"shotclip", ~CONTENTS_SOLID, 0, CONTENTS_SHOTCLIP },
{"trigger", ~CONTENTS_SOLID, 0, CONTENTS_TRIGGER },
{"nodrop", ~CONTENTS_SOLID, 0, CONTENTS_NODROP }, // don't drop items or leave bodies (death fog, lava, etc)
{"terrain", ~CONTENTS_SOLID, 0, CONTENTS_TERRAIN }, // use special terrain collsion
{"ladder", ~CONTENTS_SOLID, 0, CONTENTS_LADDER }, // climb up in it like water
{"abseil", ~CONTENTS_SOLID, 0, CONTENTS_ABSEIL }, // can abseil down this brush
{"outside", ~CONTENTS_SOLID, 0, CONTENTS_OUTSIDE }, // volume is considered to be in the outside (i.e. not indoors)
{"detail", -1, 0, CONTENTS_DETAIL }, // don't include in structural bsp
{"trans", -1, 0, CONTENTS_TRANSLUCENT }, // surface has an alpha component
};
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 |= i;
break;
}
}
}
/*
===============
ParseVector
===============
*/
static 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.
=================
*/
static 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" ) )
{
if ( !CM_ParseVector( shader, text, 3, shader->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;
}
else if ( !Q_stricmp( token, "damageShader" ) )
{
CCMShader *damageShader;
token = COM_ParseExt( text, qfalse );
if ( !token[0] )
{
Com_Printf( S_COLOR_YELLOW "WARNING: missing parm for 'damageShader' keyword in shader '%s'\n", shader->shader );
continue;
}
damageShader = CM_GetShaderInfo(token);
if(damageShader)
{
shader->damageShaderNum = damageShader - cm.shaders;
}
token = COM_ParseExt( text, qfalse );
if(token)
{
shader->damage = atof(token);
}
continue;
}
//
// location hit mesh load
//
else if ( !Q_stricmp( token, "hitLocation" ) )
{
// grab the filename of the hit location texture
token = COM_ParseExt( text, qfalse );
if ( !token[0] )
{
break;
}
Q_strncpyz(shader->hitLocation, token, MAX_QPATH);
continue;
}
//
// location hit material mesh load
//
else if ( !Q_stricmp( token, "hitMaterial" ) )
{
// grab the filename of the hit location texture
token = COM_ParseExt( text, qfalse );
if ( !token[0] )
{
break;
}
Q_strncpyz(shader->hitMaterial, token, MAX_QPATH);
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 < cm.numShaders; i++)
{
cmShaderTable.insert(CM_GetShaderInfo(i));
}
// Go through and parse evaluate shader names to shadernums
for(i = 0; i < cm.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 );
// 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 >= cm.numShaders))
{
return(NULL);
}
out = cm.shaders + shaderNum;
return(out);
}
int CM_HandleDamageShader(int sideNum, int damage)
{
CCMShader *shader;
CCMShader *damageShader;
cbrushside_t *side;
// Check to see if the face can be damaged
side = cm.brushsides + sideNum;
if(side->damage <= 0)
{
return(-1);
}
if((side->damage - damage <= 0) || (damage == -1))
{
// Damage the texture
shader = CM_GetShaderInfo(side->shaderNum);
if(shader->damageShaderNum)
{
damageShader = CM_GetShaderInfo(shader->damageShaderNum);
if(damageShader)
{
side->shaderNum = shader->damageShaderNum;
side->surfaceFlags = damageShader->surfaceFlags;
side->damage = damageShader->damage;
return(side->drawSurfNum);
}
}
return(-1);
}
side->damage -= damage;
return(-1);
}
// end