Paranoia2/utils/common/shaders.cpp

425 lines
11 KiB
C++

/*
shaders.cpp - parsing quake3 shaders for map-compile tools
Copyright (C) 2018 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.
*/
// cmdlib.c
#include "stringlib.h"
#include "cmdlib.h"
#include "mathlib.h"
#include "filesystem.h"
#include "shaders.h"
#include "scriplib.h"
#include "bspfile.h"
shaderInfo_t *shaderInfo = NULL;
int numShaderInfo;
surfaceParm_t surfaceParams[] =
{
{ "default", CONTENTS_NONE, FSHADER_DEFAULT },
{ "lightgrid", CONTENTS_NONE, FSHADER_REMOVE },
{ "antiportal", CONTENTS_NONE, FSHADER_REMOVE },
{ "skip", CONTENTS_EMPTY, FSHADER_SKIP },
{ "origin", CONTENTS_ORIGIN, FSHADER_REMOVE },
{ "areaportal", CONTENTS_NONE, FSHADER_REMOVE },
{ "trans", CONTENTS_NONE, FSHADER_DETAIL }, // FIXME: this should be CONTENTS_TRANSLUCENT
{ "detail", CONTENTS_SOLID, FSHADER_DETAIL },
{ "structural", CONTENTS_SOLID, FSHADER_DEFAULT },
{ "hint", CONTENTS_EMPTY, FSHADER_HINT },
{ "nodraw", CONTENTS_SOLID, FSHADER_NODRAW },
{ "alphashadow", CONTENTS_NONE, FSHADER_DEFAULT },
{ "lightfilter", CONTENTS_NONE, FSHADER_DEFAULT },
{ "nolightmap", CONTENTS_NONE, FSHADER_NOLIGHTMAP },
{ "pointlight", CONTENTS_NONE, FSHADER_DEFAULT },
{ "nonsolid", CONTENTS_EMPTY, FSHADER_NOCLIP },
{ "trigger", CONTENTS_SOLID, FSHADER_TRIGGER },
{ "water", CONTENTS_WATER, FSHADER_NOLIGHTMAP },
{ "slime", CONTENTS_SLIME, FSHADER_NOLIGHTMAP },
{ "lava", CONTENTS_LAVA, FSHADER_DEFAULT },
{ "clip", CONTENTS_SOLID, FSHADER_CLIP },
{ "playerclip", CONTENTS_SOLID, FSHADER_CLIP },
{ "monsterclip", CONTENTS_EMPTY, FSHADER_CLIP }, // FIXME
{ "nodrop", CONTENTS_NONE, FSHADER_REMOVE },
{ "clusterportal", CONTENTS_NONE, FSHADER_REMOVE },
{ "donotenter", CONTENTS_NONE, FSHADER_REMOVE },
{ "botclip", CONTENTS_NONE, FSHADER_REMOVE },
{ "fog", CONTENTS_FOG, FSHADER_DEFAULT },
{ "sky", CONTENTS_SKY, FSHADER_NOLIGHTMAP },
{ "slick", CONTENTS_NONE, FSHADER_DEFAULT },
{ "noimpact", CONTENTS_NONE, FSHADER_DEFAULT },
{ "nomarks", CONTENTS_NONE, FSHADER_DEFAULT },
{ "ladder", CONTENTS_NONE, FSHADER_DEFAULT },
{ "nodamage", CONTENTS_NONE, FSHADER_DEFAULT },
{ "metalsteps", CONTENTS_NONE, FSHADER_DEFAULT },
{ "snowsteps", CONTENTS_NONE, FSHADER_DEFAULT },
{ "woodsteps", CONTENTS_NONE, FSHADER_DEFAULT },
{ "dmgthrough", CONTENTS_NONE, FSHADER_DEFAULT },
{ "flesh", CONTENTS_NONE, FSHADER_DEFAULT },
{ "nosteps", CONTENTS_NONE, FSHADER_DEFAULT },
{ "nodlight", CONTENTS_NONE, FSHADER_DEFAULT },
{ "dust", CONTENTS_NONE, FSHADER_DEFAULT },
{ NULL, 0, 0 }
};
void TCModIdentity( matrix3x3 mod )
{
mod[0][0] = 1.0f;
mod[0][1] = 0.0f;
mod[0][2] = 0.0f;
mod[1][0] = 0.0f;
mod[1][1] = 1.0f;
mod[1][2] = 0.0f;
mod[2][0] = 0.0f;
mod[2][1] = 0.0f;
mod[2][2] = 1.0f;
}
/*
=================
ApplySurfaceParm
applies a named surfaceparm to the supplied flags
=================
*/
bool ApplySurfaceParm( const char *name, int *contents, int *compileFlags )
{
int fake;
surfaceParm_t *sp;
if( name == NULL )
return false;
if( contents == NULL )
contents = &fake;
if( compileFlags == NULL )
compileFlags = &fake;
for( sp = surfaceParams; sp->name != NULL; sp++ )
{
if( !Q_stricmp( name, sp->name ))
{
if( sp->contents != CONTENTS_NONE )
*contents = sp->contents;
SetBits( *compileFlags, sp->compileFlags );
return true;
}
}
return false;
}
/*
=================
AllocShaderInfo
allocates and initializes a new shader
=================
*/
static shaderInfo_t *AllocShaderInfo( void )
{
shaderInfo_t *si;
if( shaderInfo == NULL )
{
shaderInfo = (shaderInfo_t *)Mem_Alloc( sizeof( shaderInfo_t ) * MAX_SHADER_INFO );
numShaderInfo = 0;
}
if( numShaderInfo == MAX_SHADER_INFO )
COM_FatalError( "MAX_SHADER_INFO limit exceeded\n" );
si = &shaderInfo[numShaderInfo];
numShaderInfo++;
ApplySurfaceParm( "default", &si->contents, &si->flags );
si->lightmapSampleOffset = DEFAULT_LIGHTMAP_SAMPLE_OFFSET;
si->vertexShadows = true;
si->vertexScale = 1.0;
TCModIdentity( si->mod );
return si;
}
/*
=================
LoadShaderImages
loads a shader's images
=================
*/
static void LoadShaderImages( shaderInfo_t *si )
{
char shaderPath[MAX_SHADERPATH];
Q_snprintf( shaderPath, sizeof( shaderPath ), "%s.tga", si->name );
if( FS_FileExists( si->editorImagePath, false ))
Q_strncpy( si->imagePath, si->editorImagePath, sizeof( si->imagePath ));
else if( FS_FileExists( shaderPath, false ))
Q_strncpy( si->imagePath, shaderPath, sizeof( si->imagePath ));
else if( FS_FileExists( si->lightImagePath, false ))
Q_strncpy( si->imagePath, si->lightImagePath, sizeof( si->imagePath ));
else if( FS_FileExists( si->implicitImagePath, false ))
Q_strncpy( si->imagePath, si->implicitImagePath, sizeof( si->imagePath ));
// leave as error
if( !COM_CheckString( si->imagePath ))
Q_strcpy( si->imagePath, shaderPath );
if( !COM_CheckString( si->implicitImagePath ) && FS_FileExists( si->imagePath, false ))
Q_strcpy( si->implicitImagePath, si->imagePath );
}
/*
=================
FinishShader
loads a shader's images
=================
*/
static void FinishShader( shaderInfo_t *si )
{
si->finished = true;
}
/*
=================
ShaderInfoForShader
finds a shaderinfo for a named shader
=================
*/
shaderInfo_t *ShaderInfoForShader( const char *shaderName )
{
char shader[MAX_SHADERPATH];
shaderInfo_t *si;
// strip off extension
Q_snprintf( shader, sizeof( shader ), "textures/%s", shaderName );
COM_StripExtension( shader );
for( int i = 0; i < numShaderInfo; i++ )
{
si = &shaderInfo[i];
if( !Q_stricmp( shader, si->name ))
{
if( !si->finished )
{
LoadShaderImages( si );
FinishShader( si );
}
return si;
}
}
si = AllocShaderInfo();
Q_strcpy( si->name, shader );
SetBits( si->flags, FSHADER_DEFAULTED );
LoadShaderImages( si );
FinishShader( si );
return si;
}
/*
=================
ParseShaderFile
parses a shader file into discrete shaderInfo_t
=================
*/
static void ParseShaderFile( const char *filename )
{
char shaderText[16384];
char *filedata;
size_t filesize;
shaderInfo_t *si = NULL;
char *suffix;
shaderText[0] = '\0';
// load the shader
filedata = (char *)FS_LoadFile( filename, &filesize, false );
ParseFromMemory( filedata, filesize );
while ( 1 )
{
// copy shader text to the shaderinfo
if( si != NULL && shaderText[0] != '\0' )
{
Q_strcat( shaderText, "\n" );
si->shaderText = copystring( shaderText );
}
shaderText[0] = '\0';
if( !GetToken( true ))
break;
// shader name is initial token
si = AllocShaderInfo();
Q_strncpy( si->name, token, sizeof( si->name ));
// ignore ":q3map" suffix
suffix = Q_strstr( si->name, ":q3map" );
if( suffix != NULL ) *suffix = '\0';
/* handle { } section */
if( !GetTokenAppend( shaderText, true ))
break;
if( Q_strcmp( token, "{" ))
{
if( si != NULL )
{
COM_FatalError( "%s, line %d: { not found!\nFound instead: %s\nLast known shader: %s",
filename, scriptline, token, si->name );
}
else
{
COM_FatalError( "%s, line %d: { not found!\nFound instead: %s", filename, scriptline, token );
}
}
while( 1 )
{
if( !GetTokenAppend( shaderText, true ))
break;
if( !Q_strcmp( token, "}" ))
break;
// -----------------------------------------------------------------
// shader stages (passes)
// -----------------------------------------------------------------
// parse stage directives
if( !Q_strcmp( token, "{" ))
{
while ( 1 )
{
if( !GetTokenAppend( shaderText, true ))
break;
if( !Q_strcmp( token, "}" ))
break;
// digest any images
if( !Q_stricmp( token, "map" ) || !Q_stricmp( token, "clampMap" ) || !Q_stricmp( token, "animMap" ))
{
// skip one token for animated stages
if( !Q_stricmp( token, "animMap" ))
GetTokenAppend( shaderText, false );
GetTokenAppend( shaderText, false );
if( token[0] != '*' && token[0] != '$' )
{
Q_strcpy( si->implicitImagePath, token );
COM_DefaultExtension( si->implicitImagePath, ".tga" );
}
}
}
}
// -----------------------------------------------------------------
// surfaceparm * directives
// -----------------------------------------------------------------
else if( !Q_stricmp( token, "surfaceparm" ))
{
GetTokenAppend( shaderText, false );
if( !ApplySurfaceParm( token, &si->contents, &si->flags ))
MsgDev( D_WARN, "unknown surfaceparm: \"%s\"\n", token );
}
// -----------------------------------------------------------------
// image directives
// -----------------------------------------------------------------
// qer_editorimage <image>
else if( !Q_stricmp( token, "qer_editorImage" ))
{
GetTokenAppend( shaderText, false );
Q_strcpy( si->editorImagePath, token );
COM_DefaultExtension( si->editorImagePath, ".tga" );
}
// q3map_lightimage <image>
else if( !Q_stricmp( token, "q3map_lightImage" ))
{
GetTokenAppend( shaderText, false );
Q_strcpy( si->lightImagePath, token );
COM_DefaultExtension( si->lightImagePath, ".tga" );
}
// skyparms <outer image> <cloud height> <inner image>
else if( !Q_stricmp( token, "skyParms" ))
{
GetTokenAppend( shaderText, false );
if( Q_stricmp( token, "-" ) && Q_stricmp( token, "full" ))
{
Q_strcpy( si->skyParmsImageBase, token );
// use top image as sky light image
if( si->lightImagePath[0] == '\0' )
Q_sprintf( si->lightImagePath, "%s_up.tga", si->skyParmsImageBase );
}
// skip rest of line
GetTokenAppend( shaderText, false );
GetTokenAppend( shaderText, false );
}
// -----------------------------------------------------------------
// skip
// -----------------------------------------------------------------
// ignore all other tokens on the line
while( TokenAvailable() && GetTokenAppend( shaderText, false ));
}
}
Mem_Free( filedata, C_FILESYSTEM );
}
void LoadShaderInfo( void )
{
search_t *search = FS_Search( "scripts/*.shader", true, false );
if( !search ) return;
for( int i = 0; i < search->numfilenames; i++ )
ParseShaderFile( search->filenames[i] );
Mem_Free( search, C_FILESYSTEM );
MsgDev( D_INFO, "%9d shaderInfo\n", numShaderInfo );
}
void FreeShaderInfo( void )
{
shaderInfo_t *si;
for( int i = 0; i < numShaderInfo; i++ )
{
si = &shaderInfo[i];
freestring( si->shaderText );
}
Mem_Free( shaderInfo );
shaderInfo = NULL;
}