mirror of
https://github.com/id-Software/DOOM-3-BFG.git
synced 2024-12-11 13:11:47 +00:00
3421 lines
76 KiB
C++
3421 lines
76 KiB
C++
/*
|
|
===========================================================================
|
|
|
|
Doom 3 BFG Edition GPL Source Code
|
|
Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company.
|
|
|
|
This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code").
|
|
|
|
Doom 3 BFG Edition Source Code 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.
|
|
|
|
Doom 3 BFG Edition Source Code 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.
|
|
|
|
You should have received a copy of the GNU General Public License
|
|
along with Doom 3 BFG Edition Source Code. If not, see <http://www.gnu.org/licenses/>.
|
|
|
|
In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below.
|
|
|
|
If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA.
|
|
|
|
===========================================================================
|
|
*/
|
|
|
|
#pragma hdrstop
|
|
#include "precompiled.h"
|
|
|
|
|
|
#include "tr_local.h"
|
|
|
|
/*
|
|
|
|
Any errors during parsing just set MF_DEFAULTED and return, rather than throwing
|
|
a hard error. This will cause the material to fall back to default material,
|
|
but otherwise let things continue.
|
|
|
|
Each material may have a set of calculations that must be evaluated before
|
|
drawing with it.
|
|
|
|
Every expression that a material uses can be evaluated at one time, which
|
|
will allow for perfect common subexpression removal when I get around to
|
|
writing it.
|
|
|
|
Without this, scrolling an entire surface could result in evaluating the
|
|
same texture matrix calculations a half dozen times.
|
|
|
|
Open question: should I allow arbitrary per-vertex color, texCoord, and vertex
|
|
calculations to be specified in the material code?
|
|
|
|
Every stage will definately have a valid image pointer.
|
|
|
|
We might want the ability to change the sort value based on conditionals,
|
|
but it could be a hassle to implement,
|
|
|
|
*/
|
|
|
|
// keep all of these on the stack, when they are static it makes material parsing non-reentrant
|
|
typedef struct mtrParsingData_s
|
|
{
|
|
bool registerIsTemporary[MAX_EXPRESSION_REGISTERS];
|
|
float shaderRegisters[MAX_EXPRESSION_REGISTERS];
|
|
expOp_t shaderOps[MAX_EXPRESSION_OPS];
|
|
shaderStage_t parseStages[MAX_SHADER_STAGES];
|
|
|
|
bool registersAreConstant;
|
|
bool forceOverlays;
|
|
} mtrParsingData_t;
|
|
|
|
idCVar r_forceSoundOpAmplitude( "r_forceSoundOpAmplitude", "0", CVAR_FLOAT, "Don't call into the sound system for amplitudes" );
|
|
|
|
/*
|
|
=============
|
|
idMaterial::CommonInit
|
|
=============
|
|
*/
|
|
void idMaterial::CommonInit()
|
|
{
|
|
desc = "<none>";
|
|
renderBump = "";
|
|
contentFlags = CONTENTS_SOLID;
|
|
surfaceFlags = SURFTYPE_NONE;
|
|
materialFlags = 0;
|
|
sort = SS_BAD;
|
|
stereoEye = 0;
|
|
coverage = MC_BAD;
|
|
cullType = CT_FRONT_SIDED;
|
|
deform = DFRM_NONE;
|
|
numOps = 0;
|
|
ops = NULL;
|
|
numRegisters = 0;
|
|
expressionRegisters = NULL;
|
|
constantRegisters = NULL;
|
|
numStages = 0;
|
|
numAmbientStages = 0;
|
|
stages = NULL;
|
|
editorImage = NULL;
|
|
lightFalloffImage = NULL;
|
|
shouldCreateBackSides = false;
|
|
entityGui = 0;
|
|
fogLight = false;
|
|
blendLight = false;
|
|
ambientLight = false;
|
|
noFog = false;
|
|
hasSubview = false;
|
|
allowOverlays = true;
|
|
unsmoothedTangents = false;
|
|
gui = NULL;
|
|
memset( deformRegisters, 0, sizeof( deformRegisters ) );
|
|
editorAlpha = 1.0;
|
|
spectrum = 0;
|
|
polygonOffset = 0;
|
|
suppressInSubview = false;
|
|
refCount = 0;
|
|
portalSky = false;
|
|
fastPathBumpImage = NULL;
|
|
fastPathDiffuseImage = NULL;
|
|
fastPathSpecularImage = NULL;
|
|
deformDecl = NULL;
|
|
|
|
decalInfo.stayTime = 10000;
|
|
decalInfo.fadeTime = 4000;
|
|
decalInfo.start[0] = 1;
|
|
decalInfo.start[1] = 1;
|
|
decalInfo.start[2] = 1;
|
|
decalInfo.start[3] = 1;
|
|
decalInfo.end[0] = 0;
|
|
decalInfo.end[1] = 0;
|
|
decalInfo.end[2] = 0;
|
|
decalInfo.end[3] = 0;
|
|
}
|
|
|
|
|
|
/*
|
|
=============
|
|
idMaterial::idMaterial
|
|
=============
|
|
*/
|
|
idMaterial::idMaterial()
|
|
{
|
|
CommonInit();
|
|
|
|
// we put this here instead of in CommonInit, because
|
|
// we don't want it cleared when a material is purged
|
|
surfaceArea = 0;
|
|
}
|
|
|
|
/*
|
|
=============
|
|
idMaterial::~idMaterial
|
|
=============
|
|
*/
|
|
idMaterial::~idMaterial()
|
|
{
|
|
}
|
|
|
|
/*
|
|
===============
|
|
idMaterial::FreeData
|
|
===============
|
|
*/
|
|
void idMaterial::FreeData()
|
|
{
|
|
int i;
|
|
|
|
if( stages )
|
|
{
|
|
// delete any idCinematic textures
|
|
for( i = 0; i < numStages; i++ )
|
|
{
|
|
if( stages[i].texture.cinematic != NULL )
|
|
{
|
|
delete stages[i].texture.cinematic;
|
|
stages[i].texture.cinematic = NULL;
|
|
}
|
|
if( stages[i].newStage != NULL )
|
|
{
|
|
Mem_Free( stages[i].newStage );
|
|
stages[i].newStage = NULL;
|
|
}
|
|
}
|
|
R_StaticFree( stages );
|
|
stages = NULL;
|
|
}
|
|
if( expressionRegisters != NULL )
|
|
{
|
|
R_StaticFree( expressionRegisters );
|
|
expressionRegisters = NULL;
|
|
}
|
|
if( constantRegisters != NULL )
|
|
{
|
|
R_StaticFree( constantRegisters );
|
|
constantRegisters = NULL;
|
|
}
|
|
if( ops != NULL )
|
|
{
|
|
R_StaticFree( ops );
|
|
ops = NULL;
|
|
}
|
|
}
|
|
|
|
/*
|
|
==============
|
|
idMaterial::GetEditorImage
|
|
==============
|
|
*/
|
|
idImage* idMaterial::GetEditorImage() const
|
|
{
|
|
if( editorImage )
|
|
{
|
|
return editorImage;
|
|
}
|
|
|
|
// if we don't have an editorImageName, use the first stage image
|
|
if( !editorImageName.Length() )
|
|
{
|
|
// _D3XP :: First check for a diffuse image, then use the first
|
|
if( numStages && stages )
|
|
{
|
|
int i;
|
|
for( i = 0; i < numStages; i++ )
|
|
{
|
|
if( stages[i].lighting == SL_DIFFUSE )
|
|
{
|
|
editorImage = stages[i].texture.image;
|
|
break;
|
|
}
|
|
}
|
|
if( !editorImage )
|
|
{
|
|
editorImage = stages[0].texture.image;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
editorImage = globalImages->defaultImage;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// look for an explicit one
|
|
editorImage = globalImages->ImageFromFile( editorImageName, TF_DEFAULT, TR_REPEAT, TD_DEFAULT );
|
|
}
|
|
|
|
if( !editorImage )
|
|
{
|
|
editorImage = globalImages->defaultImage;
|
|
}
|
|
|
|
return editorImage;
|
|
}
|
|
|
|
|
|
// info parms
|
|
typedef struct
|
|
{
|
|
const char* name;
|
|
int clearSolid, surfaceFlags, contents;
|
|
} infoParm_t;
|
|
|
|
static infoParm_t infoParms[] =
|
|
{
|
|
// game relevant attributes
|
|
{"solid", 0, 0, CONTENTS_SOLID }, // may need to override a clearSolid
|
|
{"water", 1, 0, CONTENTS_WATER }, // used for water
|
|
{"playerclip", 0, 0, CONTENTS_PLAYERCLIP }, // solid to players
|
|
{"monsterclip", 0, 0, CONTENTS_MONSTERCLIP }, // solid to monsters
|
|
{"moveableclip", 0, 0, CONTENTS_MOVEABLECLIP }, // solid to moveable entities
|
|
{"ikclip", 0, 0, CONTENTS_IKCLIP }, // solid to IK
|
|
{"blood", 0, 0, CONTENTS_BLOOD }, // used to detect blood decals
|
|
{"trigger", 0, 0, CONTENTS_TRIGGER }, // used for triggers
|
|
{"aassolid", 0, 0, CONTENTS_AAS_SOLID }, // solid for AAS
|
|
{"aasobstacle", 0, 0, CONTENTS_AAS_OBSTACLE },// used to compile an obstacle into AAS that can be enabled/disabled
|
|
{"flashlight_trigger", 0, 0, CONTENTS_FLASHLIGHT_TRIGGER }, // used for triggers that are activated by the flashlight
|
|
{"nonsolid", 1, 0, 0 }, // clears the solid flag
|
|
{"nullNormal", 0, SURF_NULLNORMAL, 0 }, // renderbump will draw as 0x80 0x80 0x80
|
|
|
|
// utility relevant attributes
|
|
{"areaportal", 1, 0, CONTENTS_AREAPORTAL }, // divides areas
|
|
{"qer_nocarve", 1, 0, CONTENTS_NOCSG}, // don't cut brushes in editor
|
|
|
|
{"discrete", 1, SURF_DISCRETE, 0 }, // surfaces should not be automatically merged together or
|
|
// clipped to the world,
|
|
// because they represent discrete objects like gui shaders
|
|
// mirrors, or autosprites
|
|
{"noFragment", 0, SURF_NOFRAGMENT, 0 },
|
|
|
|
{"slick", 0, SURF_SLICK, 0 },
|
|
{"collision", 0, SURF_COLLISION, 0 },
|
|
{"noimpact", 0, SURF_NOIMPACT, 0 }, // don't make impact explosions or marks
|
|
{"nodamage", 0, SURF_NODAMAGE, 0 }, // no falling damage when hitting
|
|
{"ladder", 0, SURF_LADDER, 0 }, // climbable
|
|
{"nosteps", 0, SURF_NOSTEPS, 0 }, // no footsteps
|
|
|
|
// material types for particle, sound, footstep feedback
|
|
{"metal", 0, SURFTYPE_METAL, 0 }, // metal
|
|
{"stone", 0, SURFTYPE_STONE, 0 }, // stone
|
|
{"flesh", 0, SURFTYPE_FLESH, 0 }, // flesh
|
|
{"wood", 0, SURFTYPE_WOOD, 0 }, // wood
|
|
{"cardboard", 0, SURFTYPE_CARDBOARD, 0 }, // cardboard
|
|
{"liquid", 0, SURFTYPE_LIQUID, 0 }, // liquid
|
|
{"glass", 0, SURFTYPE_GLASS, 0 }, // glass
|
|
{"plastic", 0, SURFTYPE_PLASTIC, 0 }, // plastic
|
|
{"ricochet", 0, SURFTYPE_RICOCHET, 0 }, // behaves like metal but causes a ricochet sound
|
|
|
|
// unassigned surface types
|
|
{"surftype10", 0, SURFTYPE_10, 0 },
|
|
{"surftype11", 0, SURFTYPE_11, 0 },
|
|
{"surftype12", 0, SURFTYPE_12, 0 },
|
|
{"surftype13", 0, SURFTYPE_13, 0 },
|
|
{"surftype14", 0, SURFTYPE_14, 0 },
|
|
{"surftype15", 0, SURFTYPE_15, 0 },
|
|
};
|
|
|
|
static const int numInfoParms = sizeof( infoParms ) / sizeof( infoParms[0] );
|
|
|
|
|
|
/*
|
|
===============
|
|
idMaterial::CheckSurfaceParm
|
|
|
|
See if the current token matches one of the surface parm bit flags
|
|
===============
|
|
*/
|
|
bool idMaterial::CheckSurfaceParm( idToken* token )
|
|
{
|
|
|
|
for( int i = 0 ; i < numInfoParms ; i++ )
|
|
{
|
|
if( !token->Icmp( infoParms[i].name ) )
|
|
{
|
|
if( infoParms[i].surfaceFlags & SURF_TYPE_MASK )
|
|
{
|
|
// ensure we only have one surface type set
|
|
surfaceFlags &= ~SURF_TYPE_MASK;
|
|
}
|
|
surfaceFlags |= infoParms[i].surfaceFlags;
|
|
contentFlags |= infoParms[i].contents;
|
|
if( infoParms[i].clearSolid )
|
|
{
|
|
contentFlags &= ~CONTENTS_SOLID;
|
|
}
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
/*
|
|
===============
|
|
idMaterial::MatchToken
|
|
|
|
Sets defaultShader and returns false if the next token doesn't match
|
|
===============
|
|
*/
|
|
bool idMaterial::MatchToken( idLexer& src, const char* match )
|
|
{
|
|
if( !src.ExpectTokenString( match ) )
|
|
{
|
|
SetMaterialFlag( MF_DEFAULTED );
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
/*
|
|
=================
|
|
idMaterial::ParseSort
|
|
=================
|
|
*/
|
|
void idMaterial::ParseSort( idLexer& src )
|
|
{
|
|
idToken token;
|
|
|
|
if( !src.ReadTokenOnLine( &token ) )
|
|
{
|
|
src.Warning( "missing sort parameter" );
|
|
SetMaterialFlag( MF_DEFAULTED );
|
|
return;
|
|
}
|
|
|
|
if( !token.Icmp( "subview" ) )
|
|
{
|
|
sort = SS_SUBVIEW;
|
|
}
|
|
else if( !token.Icmp( "opaque" ) )
|
|
{
|
|
sort = SS_OPAQUE;
|
|
}
|
|
else if( !token.Icmp( "decal" ) )
|
|
{
|
|
sort = SS_DECAL;
|
|
}
|
|
else if( !token.Icmp( "far" ) )
|
|
{
|
|
sort = SS_FAR;
|
|
}
|
|
else if( !token.Icmp( "medium" ) )
|
|
{
|
|
sort = SS_MEDIUM;
|
|
}
|
|
else if( !token.Icmp( "close" ) )
|
|
{
|
|
sort = SS_CLOSE;
|
|
}
|
|
else if( !token.Icmp( "almostNearest" ) )
|
|
{
|
|
sort = SS_ALMOST_NEAREST;
|
|
}
|
|
else if( !token.Icmp( "nearest" ) )
|
|
{
|
|
sort = SS_NEAREST;
|
|
}
|
|
else if( !token.Icmp( "postProcess" ) )
|
|
{
|
|
sort = SS_POST_PROCESS;
|
|
}
|
|
else if( !token.Icmp( "portalSky" ) )
|
|
{
|
|
sort = SS_PORTAL_SKY;
|
|
}
|
|
else
|
|
{
|
|
sort = atof( token );
|
|
}
|
|
}
|
|
|
|
/*
|
|
=================
|
|
idMaterial::ParseStereoEye
|
|
=================
|
|
*/
|
|
void idMaterial::ParseStereoEye( idLexer& src )
|
|
{
|
|
idToken token;
|
|
|
|
if( !src.ReadTokenOnLine( &token ) )
|
|
{
|
|
src.Warning( "missing eye parameter" );
|
|
SetMaterialFlag( MF_DEFAULTED );
|
|
return;
|
|
}
|
|
|
|
if( !token.Icmp( "left" ) )
|
|
{
|
|
stereoEye = -1;
|
|
}
|
|
else if( !token.Icmp( "right" ) )
|
|
{
|
|
stereoEye = 1;
|
|
}
|
|
else
|
|
{
|
|
stereoEye = 0;
|
|
}
|
|
}
|
|
|
|
/*
|
|
=================
|
|
idMaterial::ParseDecalInfo
|
|
=================
|
|
*/
|
|
void idMaterial::ParseDecalInfo( idLexer& src )
|
|
{
|
|
idToken token;
|
|
|
|
decalInfo.stayTime = src.ParseFloat() * 1000;
|
|
decalInfo.fadeTime = src.ParseFloat() * 1000;
|
|
float start[4], end[4];
|
|
src.Parse1DMatrix( 4, start );
|
|
src.Parse1DMatrix( 4, end );
|
|
for( int i = 0 ; i < 4 ; i++ )
|
|
{
|
|
decalInfo.start[i] = start[i];
|
|
decalInfo.end[i] = end[i];
|
|
}
|
|
}
|
|
|
|
/*
|
|
=============
|
|
idMaterial::GetExpressionConstant
|
|
=============
|
|
*/
|
|
int idMaterial::GetExpressionConstant( float f )
|
|
{
|
|
int i;
|
|
|
|
for( i = EXP_REG_NUM_PREDEFINED ; i < numRegisters ; i++ )
|
|
{
|
|
if( !pd->registerIsTemporary[i] && pd->shaderRegisters[i] == f )
|
|
{
|
|
return i;
|
|
}
|
|
}
|
|
if( numRegisters == MAX_EXPRESSION_REGISTERS )
|
|
{
|
|
common->Warning( "GetExpressionConstant: material '%s' hit MAX_EXPRESSION_REGISTERS", GetName() );
|
|
SetMaterialFlag( MF_DEFAULTED );
|
|
return 0;
|
|
}
|
|
pd->registerIsTemporary[i] = false;
|
|
pd->shaderRegisters[i] = f;
|
|
numRegisters++;
|
|
|
|
return i;
|
|
}
|
|
|
|
/*
|
|
=============
|
|
idMaterial::GetExpressionTemporary
|
|
=============
|
|
*/
|
|
int idMaterial::GetExpressionTemporary()
|
|
{
|
|
if( numRegisters >= MAX_EXPRESSION_REGISTERS )
|
|
{
|
|
common->Warning( "GetExpressionTemporary: material '%s' hit MAX_EXPRESSION_REGISTERS", GetName() );
|
|
SetMaterialFlag( MF_DEFAULTED );
|
|
return 0;
|
|
}
|
|
pd->registerIsTemporary[numRegisters] = true;
|
|
numRegisters++;
|
|
return numRegisters - 1;
|
|
}
|
|
|
|
/*
|
|
=============
|
|
idMaterial::GetExpressionOp
|
|
=============
|
|
*/
|
|
expOp_t* idMaterial::GetExpressionOp()
|
|
{
|
|
if( numOps == MAX_EXPRESSION_OPS )
|
|
{
|
|
common->Warning( "GetExpressionOp: material '%s' hit MAX_EXPRESSION_OPS", GetName() );
|
|
SetMaterialFlag( MF_DEFAULTED );
|
|
return &pd->shaderOps[0];
|
|
}
|
|
|
|
return &pd->shaderOps[numOps++];
|
|
}
|
|
|
|
/*
|
|
=================
|
|
idMaterial::EmitOp
|
|
=================
|
|
*/
|
|
int idMaterial::EmitOp( int a, int b, expOpType_t opType )
|
|
{
|
|
expOp_t* op;
|
|
|
|
// optimize away identity operations
|
|
if( opType == OP_TYPE_ADD )
|
|
{
|
|
if( !pd->registerIsTemporary[a] && pd->shaderRegisters[a] == 0 )
|
|
{
|
|
return b;
|
|
}
|
|
if( !pd->registerIsTemporary[b] && pd->shaderRegisters[b] == 0 )
|
|
{
|
|
return a;
|
|
}
|
|
if( !pd->registerIsTemporary[a] && !pd->registerIsTemporary[b] )
|
|
{
|
|
return GetExpressionConstant( pd->shaderRegisters[a] + pd->shaderRegisters[b] );
|
|
}
|
|
}
|
|
if( opType == OP_TYPE_MULTIPLY )
|
|
{
|
|
if( !pd->registerIsTemporary[a] && pd->shaderRegisters[a] == 1 )
|
|
{
|
|
return b;
|
|
}
|
|
if( !pd->registerIsTemporary[a] && pd->shaderRegisters[a] == 0 )
|
|
{
|
|
return a;
|
|
}
|
|
if( !pd->registerIsTemporary[b] && pd->shaderRegisters[b] == 1 )
|
|
{
|
|
return a;
|
|
}
|
|
if( !pd->registerIsTemporary[b] && pd->shaderRegisters[b] == 0 )
|
|
{
|
|
return b;
|
|
}
|
|
if( !pd->registerIsTemporary[a] && !pd->registerIsTemporary[b] )
|
|
{
|
|
return GetExpressionConstant( pd->shaderRegisters[a] * pd->shaderRegisters[b] );
|
|
}
|
|
}
|
|
|
|
op = GetExpressionOp();
|
|
op->opType = opType;
|
|
op->a = a;
|
|
op->b = b;
|
|
op->c = GetExpressionTemporary();
|
|
|
|
return op->c;
|
|
}
|
|
|
|
/*
|
|
=================
|
|
idMaterial::ParseEmitOp
|
|
=================
|
|
*/
|
|
int idMaterial::ParseEmitOp( idLexer& src, int a, expOpType_t opType, int priority )
|
|
{
|
|
int b;
|
|
|
|
b = ParseExpressionPriority( src, priority );
|
|
return EmitOp( a, b, opType );
|
|
}
|
|
|
|
/*
|
|
=================
|
|
idMaterial::ParseTerm
|
|
|
|
Returns a register index
|
|
=================
|
|
*/
|
|
int idMaterial::ParseTerm( idLexer& src )
|
|
{
|
|
idToken token;
|
|
int a, b;
|
|
|
|
src.ReadToken( &token );
|
|
|
|
if( token == "(" )
|
|
{
|
|
a = ParseExpression( src );
|
|
MatchToken( src, ")" );
|
|
return a;
|
|
}
|
|
|
|
if( !token.Icmp( "time" ) )
|
|
{
|
|
pd->registersAreConstant = false;
|
|
return EXP_REG_TIME;
|
|
}
|
|
if( !token.Icmp( "parm0" ) )
|
|
{
|
|
pd->registersAreConstant = false;
|
|
return EXP_REG_PARM0;
|
|
}
|
|
if( !token.Icmp( "parm1" ) )
|
|
{
|
|
pd->registersAreConstant = false;
|
|
return EXP_REG_PARM1;
|
|
}
|
|
if( !token.Icmp( "parm2" ) )
|
|
{
|
|
pd->registersAreConstant = false;
|
|
return EXP_REG_PARM2;
|
|
}
|
|
if( !token.Icmp( "parm3" ) )
|
|
{
|
|
pd->registersAreConstant = false;
|
|
return EXP_REG_PARM3;
|
|
}
|
|
if( !token.Icmp( "parm4" ) )
|
|
{
|
|
pd->registersAreConstant = false;
|
|
return EXP_REG_PARM4;
|
|
}
|
|
if( !token.Icmp( "parm5" ) )
|
|
{
|
|
pd->registersAreConstant = false;
|
|
return EXP_REG_PARM5;
|
|
}
|
|
if( !token.Icmp( "parm6" ) )
|
|
{
|
|
pd->registersAreConstant = false;
|
|
return EXP_REG_PARM6;
|
|
}
|
|
if( !token.Icmp( "parm7" ) )
|
|
{
|
|
pd->registersAreConstant = false;
|
|
return EXP_REG_PARM7;
|
|
}
|
|
if( !token.Icmp( "parm8" ) )
|
|
{
|
|
pd->registersAreConstant = false;
|
|
return EXP_REG_PARM8;
|
|
}
|
|
if( !token.Icmp( "parm9" ) )
|
|
{
|
|
pd->registersAreConstant = false;
|
|
return EXP_REG_PARM9;
|
|
}
|
|
if( !token.Icmp( "parm10" ) )
|
|
{
|
|
pd->registersAreConstant = false;
|
|
return EXP_REG_PARM10;
|
|
}
|
|
if( !token.Icmp( "parm11" ) )
|
|
{
|
|
pd->registersAreConstant = false;
|
|
return EXP_REG_PARM11;
|
|
}
|
|
if( !token.Icmp( "global0" ) )
|
|
{
|
|
pd->registersAreConstant = false;
|
|
return EXP_REG_GLOBAL0;
|
|
}
|
|
if( !token.Icmp( "global1" ) )
|
|
{
|
|
pd->registersAreConstant = false;
|
|
return EXP_REG_GLOBAL1;
|
|
}
|
|
if( !token.Icmp( "global2" ) )
|
|
{
|
|
pd->registersAreConstant = false;
|
|
return EXP_REG_GLOBAL2;
|
|
}
|
|
if( !token.Icmp( "global3" ) )
|
|
{
|
|
pd->registersAreConstant = false;
|
|
return EXP_REG_GLOBAL3;
|
|
}
|
|
if( !token.Icmp( "global4" ) )
|
|
{
|
|
pd->registersAreConstant = false;
|
|
return EXP_REG_GLOBAL4;
|
|
}
|
|
if( !token.Icmp( "global5" ) )
|
|
{
|
|
pd->registersAreConstant = false;
|
|
return EXP_REG_GLOBAL5;
|
|
}
|
|
if( !token.Icmp( "global6" ) )
|
|
{
|
|
pd->registersAreConstant = false;
|
|
return EXP_REG_GLOBAL6;
|
|
}
|
|
if( !token.Icmp( "global7" ) )
|
|
{
|
|
pd->registersAreConstant = false;
|
|
return EXP_REG_GLOBAL7;
|
|
}
|
|
if( !token.Icmp( "fragmentPrograms" ) )
|
|
{
|
|
return 1.0f;
|
|
}
|
|
|
|
if( !token.Icmp( "sound" ) )
|
|
{
|
|
pd->registersAreConstant = false;
|
|
return EmitOp( 0, 0, OP_TYPE_SOUND );
|
|
}
|
|
|
|
// parse negative numbers
|
|
if( token == "-" )
|
|
{
|
|
src.ReadToken( &token );
|
|
if( token.type == TT_NUMBER || token == "." )
|
|
{
|
|
return GetExpressionConstant( -( float ) token.GetFloatValue() );
|
|
}
|
|
src.Warning( "Bad negative number '%s'", token.c_str() );
|
|
SetMaterialFlag( MF_DEFAULTED );
|
|
return 0;
|
|
}
|
|
|
|
if( token.type == TT_NUMBER || token == "." || token == "-" )
|
|
{
|
|
return GetExpressionConstant( ( float ) token.GetFloatValue() );
|
|
}
|
|
|
|
// see if it is a table name
|
|
const idDeclTable* table = static_cast<const idDeclTable*>( declManager->FindType( DECL_TABLE, token.c_str(), false ) );
|
|
if( !table )
|
|
{
|
|
src.Warning( "Bad term '%s'", token.c_str() );
|
|
SetMaterialFlag( MF_DEFAULTED );
|
|
return 0;
|
|
}
|
|
|
|
// parse a table expression
|
|
MatchToken( src, "[" );
|
|
|
|
b = ParseExpression( src );
|
|
|
|
MatchToken( src, "]" );
|
|
|
|
return EmitOp( table->Index(), b, OP_TYPE_TABLE );
|
|
}
|
|
|
|
/*
|
|
=================
|
|
idMaterial::ParseExpressionPriority
|
|
|
|
Returns a register index
|
|
=================
|
|
*/
|
|
#define TOP_PRIORITY 4
|
|
int idMaterial::ParseExpressionPriority( idLexer& src, int priority )
|
|
{
|
|
idToken token;
|
|
int a;
|
|
|
|
if( priority == 0 )
|
|
{
|
|
return ParseTerm( src );
|
|
}
|
|
|
|
a = ParseExpressionPriority( src, priority - 1 );
|
|
|
|
if( TestMaterialFlag( MF_DEFAULTED ) ) // we have a parse error
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
if( !src.ReadToken( &token ) )
|
|
{
|
|
// we won't get EOF in a real file, but we can
|
|
// when parsing from generated strings
|
|
return a;
|
|
}
|
|
|
|
if( priority == 1 && token == "*" )
|
|
{
|
|
return ParseEmitOp( src, a, OP_TYPE_MULTIPLY, priority );
|
|
}
|
|
if( priority == 1 && token == "/" )
|
|
{
|
|
return ParseEmitOp( src, a, OP_TYPE_DIVIDE, priority );
|
|
}
|
|
if( priority == 1 && token == "%" ) // implied truncate both to integer
|
|
{
|
|
return ParseEmitOp( src, a, OP_TYPE_MOD, priority );
|
|
}
|
|
if( priority == 2 && token == "+" )
|
|
{
|
|
return ParseEmitOp( src, a, OP_TYPE_ADD, priority );
|
|
}
|
|
if( priority == 2 && token == "-" )
|
|
{
|
|
return ParseEmitOp( src, a, OP_TYPE_SUBTRACT, priority );
|
|
}
|
|
if( priority == 3 && token == ">" )
|
|
{
|
|
return ParseEmitOp( src, a, OP_TYPE_GT, priority );
|
|
}
|
|
if( priority == 3 && token == ">=" )
|
|
{
|
|
return ParseEmitOp( src, a, OP_TYPE_GE, priority );
|
|
}
|
|
if( priority == 3 && token == "<" )
|
|
{
|
|
return ParseEmitOp( src, a, OP_TYPE_LT, priority );
|
|
}
|
|
if( priority == 3 && token == "<=" )
|
|
{
|
|
return ParseEmitOp( src, a, OP_TYPE_LE, priority );
|
|
}
|
|
if( priority == 3 && token == "==" )
|
|
{
|
|
return ParseEmitOp( src, a, OP_TYPE_EQ, priority );
|
|
}
|
|
if( priority == 3 && token == "!=" )
|
|
{
|
|
return ParseEmitOp( src, a, OP_TYPE_NE, priority );
|
|
}
|
|
if( priority == 4 && token == "&&" )
|
|
{
|
|
return ParseEmitOp( src, a, OP_TYPE_AND, priority );
|
|
}
|
|
if( priority == 4 && token == "||" )
|
|
{
|
|
return ParseEmitOp( src, a, OP_TYPE_OR, priority );
|
|
}
|
|
|
|
// assume that anything else terminates the expression
|
|
// not too robust error checking...
|
|
|
|
src.UnreadToken( &token );
|
|
|
|
return a;
|
|
}
|
|
|
|
/*
|
|
=================
|
|
idMaterial::ParseExpression
|
|
|
|
Returns a register index
|
|
=================
|
|
*/
|
|
int idMaterial::ParseExpression( idLexer& src )
|
|
{
|
|
return ParseExpressionPriority( src, TOP_PRIORITY );
|
|
}
|
|
|
|
|
|
/*
|
|
===============
|
|
idMaterial::ClearStage
|
|
===============
|
|
*/
|
|
void idMaterial::ClearStage( shaderStage_t* ss )
|
|
{
|
|
ss->drawStateBits = 0;
|
|
ss->conditionRegister = GetExpressionConstant( 1 );
|
|
ss->color.registers[0] =
|
|
ss->color.registers[1] =
|
|
ss->color.registers[2] =
|
|
ss->color.registers[3] = GetExpressionConstant( 1 );
|
|
}
|
|
|
|
/*
|
|
===============
|
|
idMaterial::NameToSrcBlendMode
|
|
===============
|
|
*/
|
|
int idMaterial::NameToSrcBlendMode( const idStr& name )
|
|
{
|
|
if( !name.Icmp( "GL_ONE" ) )
|
|
{
|
|
return GLS_SRCBLEND_ONE;
|
|
}
|
|
else if( !name.Icmp( "GL_ZERO" ) )
|
|
{
|
|
return GLS_SRCBLEND_ZERO;
|
|
}
|
|
else if( !name.Icmp( "GL_DST_COLOR" ) )
|
|
{
|
|
return GLS_SRCBLEND_DST_COLOR;
|
|
}
|
|
else if( !name.Icmp( "GL_ONE_MINUS_DST_COLOR" ) )
|
|
{
|
|
return GLS_SRCBLEND_ONE_MINUS_DST_COLOR;
|
|
}
|
|
else if( !name.Icmp( "GL_SRC_ALPHA" ) )
|
|
{
|
|
return GLS_SRCBLEND_SRC_ALPHA;
|
|
}
|
|
else if( !name.Icmp( "GL_ONE_MINUS_SRC_ALPHA" ) )
|
|
{
|
|
return GLS_SRCBLEND_ONE_MINUS_SRC_ALPHA;
|
|
}
|
|
else if( !name.Icmp( "GL_DST_ALPHA" ) )
|
|
{
|
|
return GLS_SRCBLEND_DST_ALPHA;
|
|
}
|
|
else if( !name.Icmp( "GL_ONE_MINUS_DST_ALPHA" ) )
|
|
{
|
|
return GLS_SRCBLEND_ONE_MINUS_DST_ALPHA;
|
|
}
|
|
else if( !name.Icmp( "GL_SRC_ALPHA_SATURATE" ) )
|
|
{
|
|
assert( 0 ); // FIX ME
|
|
return GLS_SRCBLEND_SRC_ALPHA;
|
|
}
|
|
|
|
common->Warning( "unknown blend mode '%s' in material '%s'", name.c_str(), GetName() );
|
|
SetMaterialFlag( MF_DEFAULTED );
|
|
|
|
return GLS_SRCBLEND_ONE;
|
|
}
|
|
|
|
/*
|
|
===============
|
|
idMaterial::NameToDstBlendMode
|
|
===============
|
|
*/
|
|
int idMaterial::NameToDstBlendMode( const idStr& name )
|
|
{
|
|
if( !name.Icmp( "GL_ONE" ) )
|
|
{
|
|
return GLS_DSTBLEND_ONE;
|
|
}
|
|
else if( !name.Icmp( "GL_ZERO" ) )
|
|
{
|
|
return GLS_DSTBLEND_ZERO;
|
|
}
|
|
else if( !name.Icmp( "GL_SRC_ALPHA" ) )
|
|
{
|
|
return GLS_DSTBLEND_SRC_ALPHA;
|
|
}
|
|
else if( !name.Icmp( "GL_ONE_MINUS_SRC_ALPHA" ) )
|
|
{
|
|
return GLS_DSTBLEND_ONE_MINUS_SRC_ALPHA;
|
|
}
|
|
else if( !name.Icmp( "GL_DST_ALPHA" ) )
|
|
{
|
|
return GLS_DSTBLEND_DST_ALPHA;
|
|
}
|
|
else if( !name.Icmp( "GL_ONE_MINUS_DST_ALPHA" ) )
|
|
{
|
|
return GLS_DSTBLEND_ONE_MINUS_DST_ALPHA;
|
|
}
|
|
else if( !name.Icmp( "GL_SRC_COLOR" ) )
|
|
{
|
|
return GLS_DSTBLEND_SRC_COLOR;
|
|
}
|
|
else if( !name.Icmp( "GL_ONE_MINUS_SRC_COLOR" ) )
|
|
{
|
|
return GLS_DSTBLEND_ONE_MINUS_SRC_COLOR;
|
|
}
|
|
|
|
common->Warning( "unknown blend mode '%s' in material '%s'", name.c_str(), GetName() );
|
|
SetMaterialFlag( MF_DEFAULTED );
|
|
|
|
return GLS_DSTBLEND_ONE;
|
|
}
|
|
|
|
/*
|
|
================
|
|
idMaterial::ParseBlend
|
|
================
|
|
*/
|
|
void idMaterial::ParseBlend( idLexer& src, shaderStage_t* stage )
|
|
{
|
|
idToken token;
|
|
int srcBlend, dstBlend;
|
|
|
|
if( !src.ReadToken( &token ) )
|
|
{
|
|
return;
|
|
}
|
|
|
|
// blending combinations
|
|
if( !token.Icmp( "blend" ) )
|
|
{
|
|
stage->drawStateBits = GLS_SRCBLEND_SRC_ALPHA | GLS_DSTBLEND_ONE_MINUS_SRC_ALPHA;
|
|
return;
|
|
}
|
|
if( !token.Icmp( "add" ) )
|
|
{
|
|
stage->drawStateBits = GLS_SRCBLEND_ONE | GLS_DSTBLEND_ONE;
|
|
return;
|
|
}
|
|
if( !token.Icmp( "filter" ) || !token.Icmp( "modulate" ) )
|
|
{
|
|
stage->drawStateBits = GLS_SRCBLEND_DST_COLOR | GLS_DSTBLEND_ZERO;
|
|
return;
|
|
}
|
|
if( !token.Icmp( "none" ) )
|
|
{
|
|
// none is used when defining an alpha mask that doesn't draw
|
|
stage->drawStateBits = GLS_SRCBLEND_ZERO | GLS_DSTBLEND_ONE;
|
|
return;
|
|
}
|
|
if( !token.Icmp( "bumpmap" ) )
|
|
{
|
|
stage->lighting = SL_BUMP;
|
|
return;
|
|
}
|
|
if( !token.Icmp( "diffusemap" ) )
|
|
{
|
|
stage->lighting = SL_DIFFUSE;
|
|
return;
|
|
}
|
|
if( !token.Icmp( "specularmap" ) )
|
|
{
|
|
stage->lighting = SL_SPECULAR;
|
|
return;
|
|
}
|
|
|
|
srcBlend = NameToSrcBlendMode( token );
|
|
|
|
MatchToken( src, "," );
|
|
if( !src.ReadToken( &token ) )
|
|
{
|
|
return;
|
|
}
|
|
dstBlend = NameToDstBlendMode( token );
|
|
|
|
stage->drawStateBits = srcBlend | dstBlend;
|
|
}
|
|
|
|
/*
|
|
================
|
|
idMaterial::ParseVertexParm
|
|
|
|
If there is a single value, it will be repeated across all elements
|
|
If there are two values, 3 = 0.0, 4 = 1.0
|
|
if there are three values, 4 = 1.0
|
|
================
|
|
*/
|
|
void idMaterial::ParseVertexParm( idLexer& src, newShaderStage_t* newStage )
|
|
{
|
|
idToken token;
|
|
|
|
src.ReadTokenOnLine( &token );
|
|
int parm = token.GetIntValue();
|
|
if( !token.IsNumeric() || parm < 0 || parm >= MAX_VERTEX_PARMS )
|
|
{
|
|
common->Warning( "bad vertexParm number\n" );
|
|
SetMaterialFlag( MF_DEFAULTED );
|
|
return;
|
|
}
|
|
if( parm >= newStage->numVertexParms )
|
|
{
|
|
newStage->numVertexParms = parm + 1;
|
|
}
|
|
|
|
newStage->vertexParms[parm][0] = ParseExpression( src );
|
|
|
|
src.ReadTokenOnLine( &token );
|
|
if( !token[0] || token.Icmp( "," ) )
|
|
{
|
|
newStage->vertexParms[parm][1] =
|
|
newStage->vertexParms[parm][2] =
|
|
newStage->vertexParms[parm][3] = newStage->vertexParms[parm][0];
|
|
return;
|
|
}
|
|
|
|
newStage->vertexParms[parm][1] = ParseExpression( src );
|
|
|
|
src.ReadTokenOnLine( &token );
|
|
if( !token[0] || token.Icmp( "," ) )
|
|
{
|
|
newStage->vertexParms[parm][2] = GetExpressionConstant( 0 );
|
|
newStage->vertexParms[parm][3] = GetExpressionConstant( 1 );
|
|
return;
|
|
}
|
|
|
|
newStage->vertexParms[parm][2] = ParseExpression( src );
|
|
|
|
src.ReadTokenOnLine( &token );
|
|
if( !token[0] || token.Icmp( "," ) )
|
|
{
|
|
newStage->vertexParms[parm][3] = GetExpressionConstant( 1 );
|
|
return;
|
|
}
|
|
|
|
newStage->vertexParms[parm][3] = ParseExpression( src );
|
|
}
|
|
|
|
/*
|
|
================
|
|
idMaterial::ParseVertexParm2
|
|
================
|
|
*/
|
|
void idMaterial::ParseVertexParm2( idLexer& src, newShaderStage_t* newStage )
|
|
{
|
|
idToken token;
|
|
src.ReadTokenOnLine( &token );
|
|
int parm = token.GetIntValue();
|
|
if( !token.IsNumeric() || parm < 0 || parm >= MAX_VERTEX_PARMS )
|
|
{
|
|
common->Warning( "bad vertexParm number\n" );
|
|
SetMaterialFlag( MF_DEFAULTED );
|
|
return;
|
|
}
|
|
|
|
if( parm >= newStage->numVertexParms )
|
|
{
|
|
newStage->numVertexParms = parm + 1;
|
|
}
|
|
|
|
newStage->vertexParms[parm][0] = ParseExpression( src );
|
|
MatchToken( src, "," );
|
|
newStage->vertexParms[parm][1] = ParseExpression( src );
|
|
MatchToken( src, "," );
|
|
newStage->vertexParms[parm][2] = ParseExpression( src );
|
|
MatchToken( src, "," );
|
|
newStage->vertexParms[parm][3] = ParseExpression( src );
|
|
}
|
|
|
|
|
|
/*
|
|
================
|
|
idMaterial::ParseFragmentMap
|
|
================
|
|
*/
|
|
void idMaterial::ParseFragmentMap( idLexer& src, newShaderStage_t* newStage )
|
|
{
|
|
const char* str;
|
|
textureFilter_t tf;
|
|
textureRepeat_t trp;
|
|
textureUsage_t td;
|
|
cubeFiles_t cubeMap;
|
|
idToken token;
|
|
|
|
tf = TF_DEFAULT;
|
|
trp = TR_REPEAT;
|
|
td = TD_DEFAULT;
|
|
cubeMap = CF_2D;
|
|
|
|
src.ReadTokenOnLine( &token );
|
|
int unit = token.GetIntValue();
|
|
if( !token.IsNumeric() || unit < 0 || unit >= MAX_FRAGMENT_IMAGES )
|
|
{
|
|
common->Warning( "bad fragmentMap number\n" );
|
|
SetMaterialFlag( MF_DEFAULTED );
|
|
return;
|
|
}
|
|
|
|
// unit 1 is the normal map.. make sure it gets flagged as the proper depth
|
|
if( unit == 1 )
|
|
{
|
|
td = TD_BUMP;
|
|
}
|
|
|
|
if( unit >= newStage->numFragmentProgramImages )
|
|
{
|
|
newStage->numFragmentProgramImages = unit + 1;
|
|
}
|
|
|
|
while( 1 )
|
|
{
|
|
src.ReadTokenOnLine( &token );
|
|
|
|
if( !token.Icmp( "cubeMap" ) )
|
|
{
|
|
cubeMap = CF_NATIVE;
|
|
continue;
|
|
}
|
|
if( !token.Icmp( "cameraCubeMap" ) )
|
|
{
|
|
cubeMap = CF_CAMERA;
|
|
continue;
|
|
}
|
|
if( !token.Icmp( "nearest" ) )
|
|
{
|
|
tf = TF_NEAREST;
|
|
continue;
|
|
}
|
|
if( !token.Icmp( "linear" ) )
|
|
{
|
|
tf = TF_LINEAR;
|
|
continue;
|
|
}
|
|
if( !token.Icmp( "clamp" ) )
|
|
{
|
|
trp = TR_CLAMP;
|
|
continue;
|
|
}
|
|
if( !token.Icmp( "noclamp" ) )
|
|
{
|
|
trp = TR_REPEAT;
|
|
continue;
|
|
}
|
|
if( !token.Icmp( "zeroclamp" ) )
|
|
{
|
|
trp = TR_CLAMP_TO_ZERO;
|
|
continue;
|
|
}
|
|
if( !token.Icmp( "alphazeroclamp" ) )
|
|
{
|
|
trp = TR_CLAMP_TO_ZERO_ALPHA;
|
|
continue;
|
|
}
|
|
if( !token.Icmp( "forceHighQuality" ) )
|
|
{
|
|
continue;
|
|
}
|
|
if( !token.Icmp( "highquality" ) )
|
|
{
|
|
continue;
|
|
}
|
|
if( !token.Icmp( "uncompressed" ) )
|
|
{
|
|
continue;
|
|
}
|
|
if( !token.Icmp( "nopicmip" ) )
|
|
{
|
|
continue;
|
|
}
|
|
|
|
// assume anything else is the image name
|
|
src.UnreadToken( &token );
|
|
break;
|
|
}
|
|
str = R_ParsePastImageProgram( src );
|
|
|
|
newStage->fragmentProgramImages[unit] =
|
|
globalImages->ImageFromFile( str, tf, trp, td, cubeMap );
|
|
if( !newStage->fragmentProgramImages[unit] )
|
|
{
|
|
newStage->fragmentProgramImages[unit] = globalImages->defaultImage;
|
|
}
|
|
}
|
|
|
|
/*
|
|
===============
|
|
idMaterial::MultiplyTextureMatrix
|
|
===============
|
|
*/
|
|
void idMaterial::MultiplyTextureMatrix( textureStage_t* ts, int registers[2][3] )
|
|
{
|
|
int old[2][3];
|
|
|
|
if( !ts->hasMatrix )
|
|
{
|
|
ts->hasMatrix = true;
|
|
memcpy( ts->matrix, registers, sizeof( ts->matrix ) );
|
|
return;
|
|
}
|
|
|
|
memcpy( old, ts->matrix, sizeof( old ) );
|
|
|
|
// multiply the two maticies
|
|
ts->matrix[0][0] = EmitOp(
|
|
EmitOp( old[0][0], registers[0][0], OP_TYPE_MULTIPLY ),
|
|
EmitOp( old[0][1], registers[1][0], OP_TYPE_MULTIPLY ), OP_TYPE_ADD );
|
|
ts->matrix[0][1] = EmitOp(
|
|
EmitOp( old[0][0], registers[0][1], OP_TYPE_MULTIPLY ),
|
|
EmitOp( old[0][1], registers[1][1], OP_TYPE_MULTIPLY ), OP_TYPE_ADD );
|
|
ts->matrix[0][2] = EmitOp(
|
|
EmitOp(
|
|
EmitOp( old[0][0], registers[0][2], OP_TYPE_MULTIPLY ),
|
|
EmitOp( old[0][1], registers[1][2], OP_TYPE_MULTIPLY ), OP_TYPE_ADD ),
|
|
old[0][2], OP_TYPE_ADD );
|
|
|
|
ts->matrix[1][0] = EmitOp(
|
|
EmitOp( old[1][0], registers[0][0], OP_TYPE_MULTIPLY ),
|
|
EmitOp( old[1][1], registers[1][0], OP_TYPE_MULTIPLY ), OP_TYPE_ADD );
|
|
ts->matrix[1][1] = EmitOp(
|
|
EmitOp( old[1][0], registers[0][1], OP_TYPE_MULTIPLY ),
|
|
EmitOp( old[1][1], registers[1][1], OP_TYPE_MULTIPLY ), OP_TYPE_ADD );
|
|
ts->matrix[1][2] = EmitOp(
|
|
EmitOp(
|
|
EmitOp( old[1][0], registers[0][2], OP_TYPE_MULTIPLY ),
|
|
EmitOp( old[1][1], registers[1][2], OP_TYPE_MULTIPLY ), OP_TYPE_ADD ),
|
|
old[1][2], OP_TYPE_ADD );
|
|
|
|
}
|
|
|
|
/*
|
|
=================
|
|
idMaterial::ParseStage
|
|
|
|
An open brace has been parsed
|
|
|
|
|
|
{
|
|
if <expression>
|
|
map <imageprogram>
|
|
"nearest" "linear" "clamp" "zeroclamp" "uncompressed" "highquality" "nopicmip"
|
|
scroll, scale, rotate
|
|
}
|
|
|
|
=================
|
|
*/
|
|
void idMaterial::ParseStage( idLexer& src, const textureRepeat_t trpDefault )
|
|
{
|
|
idToken token;
|
|
const char* str;
|
|
shaderStage_t* ss;
|
|
textureStage_t* ts;
|
|
textureFilter_t tf;
|
|
textureRepeat_t trp;
|
|
textureUsage_t td;
|
|
cubeFiles_t cubeMap;
|
|
char imageName[MAX_IMAGE_NAME];
|
|
int a, b;
|
|
int matrix[2][3];
|
|
newShaderStage_t newStage;
|
|
|
|
if( numStages >= MAX_SHADER_STAGES )
|
|
{
|
|
SetMaterialFlag( MF_DEFAULTED );
|
|
common->Warning( "material '%s' exceeded %i stages", GetName(), MAX_SHADER_STAGES );
|
|
}
|
|
|
|
tf = TF_DEFAULT;
|
|
trp = trpDefault;
|
|
td = TD_DEFAULT;
|
|
cubeMap = CF_2D;
|
|
|
|
imageName[0] = 0;
|
|
|
|
memset( &newStage, 0, sizeof( newStage ) );
|
|
newStage.glslProgram = -1;
|
|
|
|
ss = &pd->parseStages[numStages];
|
|
ts = &ss->texture;
|
|
|
|
ClearStage( ss );
|
|
|
|
while( 1 )
|
|
{
|
|
if( TestMaterialFlag( MF_DEFAULTED ) ) // we have a parse error
|
|
{
|
|
return;
|
|
}
|
|
if( !src.ExpectAnyToken( &token ) )
|
|
{
|
|
SetMaterialFlag( MF_DEFAULTED );
|
|
return;
|
|
}
|
|
|
|
// the close brace for the entire material ends the draw block
|
|
if( token == "}" )
|
|
{
|
|
break;
|
|
}
|
|
|
|
//BSM Nerve: Added for stage naming in the material editor
|
|
if( !token.Icmp( "name" ) )
|
|
{
|
|
src.SkipRestOfLine();
|
|
continue;
|
|
}
|
|
|
|
// image options
|
|
if( !token.Icmp( "blend" ) )
|
|
{
|
|
ParseBlend( src, ss );
|
|
continue;
|
|
}
|
|
|
|
if( !token.Icmp( "map" ) )
|
|
{
|
|
str = R_ParsePastImageProgram( src );
|
|
idStr::Copynz( imageName, str, sizeof( imageName ) );
|
|
continue;
|
|
}
|
|
|
|
if( !token.Icmp( "remoteRenderMap" ) )
|
|
{
|
|
ts->dynamic = DI_REMOTE_RENDER;
|
|
ts->width = src.ParseInt();
|
|
ts->height = src.ParseInt();
|
|
continue;
|
|
}
|
|
|
|
if( !token.Icmp( "mirrorRenderMap" ) )
|
|
{
|
|
ts->dynamic = DI_MIRROR_RENDER;
|
|
ts->width = src.ParseInt();
|
|
ts->height = src.ParseInt();
|
|
ts->texgen = TG_SCREEN;
|
|
continue;
|
|
}
|
|
|
|
if( !token.Icmp( "xrayRenderMap" ) )
|
|
{
|
|
ts->dynamic = DI_XRAY_RENDER;
|
|
ts->width = src.ParseInt();
|
|
ts->height = src.ParseInt();
|
|
ts->texgen = TG_SCREEN;
|
|
continue;
|
|
}
|
|
if( !token.Icmp( "screen" ) )
|
|
{
|
|
ts->texgen = TG_SCREEN;
|
|
continue;
|
|
}
|
|
if( !token.Icmp( "screen2" ) )
|
|
{
|
|
ts->texgen = TG_SCREEN2;
|
|
continue;
|
|
}
|
|
if( !token.Icmp( "glassWarp" ) )
|
|
{
|
|
ts->texgen = TG_GLASSWARP;
|
|
continue;
|
|
}
|
|
|
|
if( !token.Icmp( "videomap" ) )
|
|
{
|
|
// note that videomaps will always be in clamp mode, so texture
|
|
// coordinates had better be in the 0 to 1 range
|
|
if( !src.ReadToken( &token ) )
|
|
{
|
|
common->Warning( "missing parameter for 'videoMap' keyword in material '%s'", GetName() );
|
|
continue;
|
|
}
|
|
bool loop = false;
|
|
if( !token.Icmp( "loop" ) )
|
|
{
|
|
loop = true;
|
|
if( !src.ReadToken( &token ) )
|
|
{
|
|
common->Warning( "missing parameter for 'videoMap' keyword in material '%s'", GetName() );
|
|
continue;
|
|
}
|
|
}
|
|
ts->cinematic = idCinematic::Alloc();
|
|
ts->cinematic->InitFromFile( token.c_str(), loop );
|
|
continue;
|
|
}
|
|
|
|
if( !token.Icmp( "soundmap" ) )
|
|
{
|
|
if( !src.ReadToken( &token ) )
|
|
{
|
|
common->Warning( "missing parameter for 'soundmap' keyword in material '%s'", GetName() );
|
|
continue;
|
|
}
|
|
ts->cinematic = new( TAG_MATERIAL ) idSndWindow();
|
|
ts->cinematic->InitFromFile( token.c_str(), true );
|
|
continue;
|
|
}
|
|
|
|
if( !token.Icmp( "cubeMap" ) )
|
|
{
|
|
str = R_ParsePastImageProgram( src );
|
|
idStr::Copynz( imageName, str, sizeof( imageName ) );
|
|
cubeMap = CF_NATIVE;
|
|
continue;
|
|
}
|
|
|
|
if( !token.Icmp( "cameraCubeMap" ) )
|
|
{
|
|
str = R_ParsePastImageProgram( src );
|
|
idStr::Copynz( imageName, str, sizeof( imageName ) );
|
|
cubeMap = CF_CAMERA;
|
|
continue;
|
|
}
|
|
|
|
if( !token.Icmp( "ignoreAlphaTest" ) )
|
|
{
|
|
ss->ignoreAlphaTest = true;
|
|
continue;
|
|
}
|
|
if( !token.Icmp( "nearest" ) )
|
|
{
|
|
tf = TF_NEAREST;
|
|
continue;
|
|
}
|
|
if( !token.Icmp( "linear" ) )
|
|
{
|
|
tf = TF_LINEAR;
|
|
continue;
|
|
}
|
|
if( !token.Icmp( "clamp" ) )
|
|
{
|
|
trp = TR_CLAMP;
|
|
continue;
|
|
}
|
|
if( !token.Icmp( "noclamp" ) )
|
|
{
|
|
trp = TR_REPEAT;
|
|
continue;
|
|
}
|
|
if( !token.Icmp( "zeroclamp" ) )
|
|
{
|
|
trp = TR_CLAMP_TO_ZERO;
|
|
continue;
|
|
}
|
|
if( !token.Icmp( "alphazeroclamp" ) )
|
|
{
|
|
trp = TR_CLAMP_TO_ZERO_ALPHA;
|
|
continue;
|
|
}
|
|
if( !token.Icmp( "forceHighQuality" ) )
|
|
{
|
|
continue;
|
|
}
|
|
if( !token.Icmp( "highquality" ) )
|
|
{
|
|
continue;
|
|
}
|
|
if( !token.Icmp( "uncompressed" ) )
|
|
{
|
|
continue;
|
|
}
|
|
if( !token.Icmp( "nopicmip" ) )
|
|
{
|
|
continue;
|
|
}
|
|
if( !token.Icmp( "vertexColor" ) )
|
|
{
|
|
ss->vertexColor = SVC_MODULATE;
|
|
continue;
|
|
}
|
|
if( !token.Icmp( "inverseVertexColor" ) )
|
|
{
|
|
ss->vertexColor = SVC_INVERSE_MODULATE;
|
|
continue;
|
|
}
|
|
|
|
// privatePolygonOffset
|
|
else if( !token.Icmp( "privatePolygonOffset" ) )
|
|
{
|
|
if( !src.ReadTokenOnLine( &token ) )
|
|
{
|
|
ss->privatePolygonOffset = 1;
|
|
continue;
|
|
}
|
|
// explict larger (or negative) offset
|
|
src.UnreadToken( &token );
|
|
ss->privatePolygonOffset = src.ParseFloat();
|
|
continue;
|
|
}
|
|
|
|
// texture coordinate generation
|
|
if( !token.Icmp( "texGen" ) )
|
|
{
|
|
src.ExpectAnyToken( &token );
|
|
if( !token.Icmp( "normal" ) )
|
|
{
|
|
ts->texgen = TG_DIFFUSE_CUBE;
|
|
}
|
|
else if( !token.Icmp( "reflect" ) )
|
|
{
|
|
ts->texgen = TG_REFLECT_CUBE;
|
|
}
|
|
else if( !token.Icmp( "skybox" ) )
|
|
{
|
|
ts->texgen = TG_SKYBOX_CUBE;
|
|
}
|
|
else if( !token.Icmp( "wobbleSky" ) )
|
|
{
|
|
ts->texgen = TG_WOBBLESKY_CUBE;
|
|
texGenRegisters[0] = ParseExpression( src );
|
|
texGenRegisters[1] = ParseExpression( src );
|
|
texGenRegisters[2] = ParseExpression( src );
|
|
}
|
|
else
|
|
{
|
|
common->Warning( "bad texGen '%s' in material %s", token.c_str(), GetName() );
|
|
SetMaterialFlag( MF_DEFAULTED );
|
|
}
|
|
continue;
|
|
}
|
|
if( !token.Icmp( "scroll" ) || !token.Icmp( "translate" ) )
|
|
{
|
|
a = ParseExpression( src );
|
|
MatchToken( src, "," );
|
|
b = ParseExpression( src );
|
|
matrix[0][0] = GetExpressionConstant( 1 );
|
|
matrix[0][1] = GetExpressionConstant( 0 );
|
|
matrix[0][2] = a;
|
|
matrix[1][0] = GetExpressionConstant( 0 );
|
|
matrix[1][1] = GetExpressionConstant( 1 );
|
|
matrix[1][2] = b;
|
|
|
|
MultiplyTextureMatrix( ts, matrix );
|
|
continue;
|
|
}
|
|
if( !token.Icmp( "scale" ) )
|
|
{
|
|
a = ParseExpression( src );
|
|
MatchToken( src, "," );
|
|
b = ParseExpression( src );
|
|
// this just scales without a centering
|
|
matrix[0][0] = a;
|
|
matrix[0][1] = GetExpressionConstant( 0 );
|
|
matrix[0][2] = GetExpressionConstant( 0 );
|
|
matrix[1][0] = GetExpressionConstant( 0 );
|
|
matrix[1][1] = b;
|
|
matrix[1][2] = GetExpressionConstant( 0 );
|
|
|
|
MultiplyTextureMatrix( ts, matrix );
|
|
continue;
|
|
}
|
|
if( !token.Icmp( "centerScale" ) )
|
|
{
|
|
a = ParseExpression( src );
|
|
MatchToken( src, "," );
|
|
b = ParseExpression( src );
|
|
// this subtracts 0.5, then scales, then adds 0.5
|
|
matrix[0][0] = a;
|
|
matrix[0][1] = GetExpressionConstant( 0 );
|
|
matrix[0][2] = EmitOp( GetExpressionConstant( 0.5 ), EmitOp( GetExpressionConstant( 0.5 ), a, OP_TYPE_MULTIPLY ), OP_TYPE_SUBTRACT );
|
|
matrix[1][0] = GetExpressionConstant( 0 );
|
|
matrix[1][1] = b;
|
|
matrix[1][2] = EmitOp( GetExpressionConstant( 0.5 ), EmitOp( GetExpressionConstant( 0.5 ), b, OP_TYPE_MULTIPLY ), OP_TYPE_SUBTRACT );
|
|
|
|
MultiplyTextureMatrix( ts, matrix );
|
|
continue;
|
|
}
|
|
if( !token.Icmp( "shear" ) )
|
|
{
|
|
a = ParseExpression( src );
|
|
MatchToken( src, "," );
|
|
b = ParseExpression( src );
|
|
// this subtracts 0.5, then shears, then adds 0.5
|
|
matrix[0][0] = GetExpressionConstant( 1 );
|
|
matrix[0][1] = a;
|
|
matrix[0][2] = EmitOp( GetExpressionConstant( -0.5 ), a, OP_TYPE_MULTIPLY );
|
|
matrix[1][0] = b;
|
|
matrix[1][1] = GetExpressionConstant( 1 );
|
|
matrix[1][2] = EmitOp( GetExpressionConstant( -0.5 ), b, OP_TYPE_MULTIPLY );
|
|
|
|
MultiplyTextureMatrix( ts, matrix );
|
|
continue;
|
|
}
|
|
if( !token.Icmp( "rotate" ) )
|
|
{
|
|
const idDeclTable* table;
|
|
int sinReg, cosReg;
|
|
|
|
// in cycles
|
|
a = ParseExpression( src );
|
|
|
|
table = static_cast<const idDeclTable*>( declManager->FindType( DECL_TABLE, "sinTable", false ) );
|
|
if( !table )
|
|
{
|
|
common->Warning( "no sinTable for rotate defined" );
|
|
SetMaterialFlag( MF_DEFAULTED );
|
|
return;
|
|
}
|
|
sinReg = EmitOp( table->Index(), a, OP_TYPE_TABLE );
|
|
|
|
table = static_cast<const idDeclTable*>( declManager->FindType( DECL_TABLE, "cosTable", false ) );
|
|
if( !table )
|
|
{
|
|
common->Warning( "no cosTable for rotate defined" );
|
|
SetMaterialFlag( MF_DEFAULTED );
|
|
return;
|
|
}
|
|
cosReg = EmitOp( table->Index(), a, OP_TYPE_TABLE );
|
|
|
|
// this subtracts 0.5, then rotates, then adds 0.5
|
|
matrix[0][0] = cosReg;
|
|
matrix[0][1] = EmitOp( GetExpressionConstant( 0 ), sinReg, OP_TYPE_SUBTRACT );
|
|
matrix[0][2] = EmitOp( EmitOp( EmitOp( GetExpressionConstant( -0.5 ), cosReg, OP_TYPE_MULTIPLY ),
|
|
EmitOp( GetExpressionConstant( 0.5 ), sinReg, OP_TYPE_MULTIPLY ), OP_TYPE_ADD ),
|
|
GetExpressionConstant( 0.5 ), OP_TYPE_ADD );
|
|
|
|
matrix[1][0] = sinReg;
|
|
matrix[1][1] = cosReg;
|
|
matrix[1][2] = EmitOp( EmitOp( EmitOp( GetExpressionConstant( -0.5 ), sinReg, OP_TYPE_MULTIPLY ),
|
|
EmitOp( GetExpressionConstant( -0.5 ), cosReg, OP_TYPE_MULTIPLY ), OP_TYPE_ADD ),
|
|
GetExpressionConstant( 0.5 ), OP_TYPE_ADD );
|
|
|
|
MultiplyTextureMatrix( ts, matrix );
|
|
continue;
|
|
}
|
|
|
|
// color mask options
|
|
if( !token.Icmp( "maskRed" ) )
|
|
{
|
|
ss->drawStateBits |= GLS_REDMASK;
|
|
continue;
|
|
}
|
|
if( !token.Icmp( "maskGreen" ) )
|
|
{
|
|
ss->drawStateBits |= GLS_GREENMASK;
|
|
continue;
|
|
}
|
|
if( !token.Icmp( "maskBlue" ) )
|
|
{
|
|
ss->drawStateBits |= GLS_BLUEMASK;
|
|
continue;
|
|
}
|
|
if( !token.Icmp( "maskAlpha" ) )
|
|
{
|
|
ss->drawStateBits |= GLS_ALPHAMASK;
|
|
continue;
|
|
}
|
|
if( !token.Icmp( "maskColor" ) )
|
|
{
|
|
ss->drawStateBits |= GLS_COLORMASK;
|
|
continue;
|
|
}
|
|
if( !token.Icmp( "maskDepth" ) )
|
|
{
|
|
ss->drawStateBits |= GLS_DEPTHMASK;
|
|
continue;
|
|
}
|
|
if( !token.Icmp( "alphaTest" ) )
|
|
{
|
|
ss->hasAlphaTest = true;
|
|
ss->alphaTestRegister = ParseExpression( src );
|
|
coverage = MC_PERFORATED;
|
|
continue;
|
|
}
|
|
|
|
// shorthand for 2D modulated
|
|
if( !token.Icmp( "colored" ) )
|
|
{
|
|
ss->color.registers[0] = EXP_REG_PARM0;
|
|
ss->color.registers[1] = EXP_REG_PARM1;
|
|
ss->color.registers[2] = EXP_REG_PARM2;
|
|
ss->color.registers[3] = EXP_REG_PARM3;
|
|
pd->registersAreConstant = false;
|
|
continue;
|
|
}
|
|
|
|
if( !token.Icmp( "color" ) )
|
|
{
|
|
ss->color.registers[0] = ParseExpression( src );
|
|
MatchToken( src, "," );
|
|
ss->color.registers[1] = ParseExpression( src );
|
|
MatchToken( src, "," );
|
|
ss->color.registers[2] = ParseExpression( src );
|
|
MatchToken( src, "," );
|
|
ss->color.registers[3] = ParseExpression( src );
|
|
continue;
|
|
}
|
|
if( !token.Icmp( "red" ) )
|
|
{
|
|
ss->color.registers[0] = ParseExpression( src );
|
|
continue;
|
|
}
|
|
if( !token.Icmp( "green" ) )
|
|
{
|
|
ss->color.registers[1] = ParseExpression( src );
|
|
continue;
|
|
}
|
|
if( !token.Icmp( "blue" ) )
|
|
{
|
|
ss->color.registers[2] = ParseExpression( src );
|
|
continue;
|
|
}
|
|
if( !token.Icmp( "alpha" ) )
|
|
{
|
|
ss->color.registers[3] = ParseExpression( src );
|
|
continue;
|
|
}
|
|
if( !token.Icmp( "rgb" ) )
|
|
{
|
|
ss->color.registers[0] = ss->color.registers[1] =
|
|
ss->color.registers[2] = ParseExpression( src );
|
|
continue;
|
|
}
|
|
if( !token.Icmp( "rgba" ) )
|
|
{
|
|
ss->color.registers[0] = ss->color.registers[1] =
|
|
ss->color.registers[2] = ss->color.registers[3] = ParseExpression( src );
|
|
continue;
|
|
}
|
|
|
|
if( !token.Icmp( "if" ) )
|
|
{
|
|
ss->conditionRegister = ParseExpression( src );
|
|
continue;
|
|
}
|
|
if( !token.Icmp( "program" ) )
|
|
{
|
|
if( src.ReadTokenOnLine( &token ) )
|
|
{
|
|
newStage.vertexProgram = renderProgManager.FindVertexShader( token.c_str() );
|
|
newStage.fragmentProgram = renderProgManager.FindFragmentShader( token.c_str() );
|
|
}
|
|
continue;
|
|
}
|
|
if( !token.Icmp( "fragmentProgram" ) )
|
|
{
|
|
if( src.ReadTokenOnLine( &token ) )
|
|
{
|
|
newStage.fragmentProgram = renderProgManager.FindFragmentShader( token.c_str() );
|
|
}
|
|
continue;
|
|
}
|
|
if( !token.Icmp( "vertexProgram" ) )
|
|
{
|
|
if( src.ReadTokenOnLine( &token ) )
|
|
{
|
|
newStage.vertexProgram = renderProgManager.FindVertexShader( token.c_str() );
|
|
}
|
|
continue;
|
|
}
|
|
|
|
if( !token.Icmp( "vertexParm2" ) )
|
|
{
|
|
ParseVertexParm2( src, &newStage );
|
|
continue;
|
|
}
|
|
|
|
if( !token.Icmp( "vertexParm" ) )
|
|
{
|
|
ParseVertexParm( src, &newStage );
|
|
continue;
|
|
}
|
|
|
|
if( !token.Icmp( "fragmentMap" ) )
|
|
{
|
|
ParseFragmentMap( src, &newStage );
|
|
continue;
|
|
}
|
|
|
|
|
|
common->Warning( "unknown token '%s' in material '%s'", token.c_str(), GetName() );
|
|
SetMaterialFlag( MF_DEFAULTED );
|
|
return;
|
|
}
|
|
|
|
|
|
// if we are using newStage, allocate a copy of it
|
|
if( newStage.fragmentProgram || newStage.vertexProgram )
|
|
{
|
|
newStage.glslProgram = renderProgManager.FindGLSLProgram( GetName(), newStage.vertexProgram, newStage.fragmentProgram );
|
|
ss->newStage = ( newShaderStage_t* )Mem_Alloc( sizeof( newStage ), TAG_MATERIAL );
|
|
*( ss->newStage ) = newStage;
|
|
}
|
|
|
|
// successfully parsed a stage
|
|
numStages++;
|
|
|
|
// select a compressed depth based on what the stage is
|
|
if( td == TD_DEFAULT )
|
|
{
|
|
switch( ss->lighting )
|
|
{
|
|
case SL_BUMP:
|
|
td = TD_BUMP;
|
|
break;
|
|
case SL_DIFFUSE:
|
|
td = TD_DIFFUSE;
|
|
break;
|
|
case SL_SPECULAR:
|
|
td = TD_SPECULAR;
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
// create a new coverage stage on the fly - copy all data from the current stage
|
|
if( ( td == TD_DIFFUSE ) && ss->hasAlphaTest )
|
|
{
|
|
// create new coverage stage
|
|
shaderStage_t* newCoverageStage = &pd->parseStages[numStages];
|
|
numStages++;
|
|
// copy it
|
|
*newCoverageStage = *ss;
|
|
// toggle alphatest off for the current stage so it doesn't get called during the depth fill pass
|
|
ss->hasAlphaTest = false;
|
|
// toggle alpha test on for the coverage stage
|
|
newCoverageStage->hasAlphaTest = true;
|
|
newCoverageStage->lighting = SL_COVERAGE;
|
|
textureStage_t* coverageTS = &newCoverageStage->texture;
|
|
|
|
// now load the image with all the parms we parsed for the coverage stage
|
|
if( imageName[0] )
|
|
{
|
|
coverageTS->image = globalImages->ImageFromFile( imageName, tf, trp, TD_COVERAGE, cubeMap );
|
|
if( !coverageTS->image )
|
|
{
|
|
coverageTS->image = globalImages->defaultImage;
|
|
}
|
|
}
|
|
else if( !coverageTS->cinematic && !coverageTS->dynamic && !ss->newStage )
|
|
{
|
|
common->Warning( "material '%s' had stage with no image", GetName() );
|
|
coverageTS->image = globalImages->defaultImage;
|
|
}
|
|
}
|
|
|
|
// now load the image with all the parms we parsed
|
|
if( imageName[0] )
|
|
{
|
|
ts->image = globalImages->ImageFromFile( imageName, tf, trp, td, cubeMap );
|
|
if( !ts->image )
|
|
{
|
|
ts->image = globalImages->defaultImage;
|
|
}
|
|
}
|
|
else if( !ts->cinematic && !ts->dynamic && !ss->newStage )
|
|
{
|
|
common->Warning( "material '%s' had stage with no image", GetName() );
|
|
ts->image = globalImages->defaultImage;
|
|
}
|
|
}
|
|
|
|
/*
|
|
===============
|
|
idMaterial::ParseDeform
|
|
===============
|
|
*/
|
|
void idMaterial::ParseDeform( idLexer& src )
|
|
{
|
|
idToken token;
|
|
|
|
if( !src.ExpectAnyToken( &token ) )
|
|
{
|
|
return;
|
|
}
|
|
|
|
if( !token.Icmp( "sprite" ) )
|
|
{
|
|
deform = DFRM_SPRITE;
|
|
cullType = CT_TWO_SIDED;
|
|
SetMaterialFlag( MF_NOSHADOWS );
|
|
return;
|
|
}
|
|
if( !token.Icmp( "tube" ) )
|
|
{
|
|
deform = DFRM_TUBE;
|
|
cullType = CT_TWO_SIDED;
|
|
SetMaterialFlag( MF_NOSHADOWS );
|
|
return;
|
|
}
|
|
if( !token.Icmp( "flare" ) )
|
|
{
|
|
deform = DFRM_FLARE;
|
|
cullType = CT_TWO_SIDED;
|
|
deformRegisters[0] = ParseExpression( src );
|
|
SetMaterialFlag( MF_NOSHADOWS );
|
|
return;
|
|
}
|
|
if( !token.Icmp( "expand" ) )
|
|
{
|
|
deform = DFRM_EXPAND;
|
|
deformRegisters[0] = ParseExpression( src );
|
|
return;
|
|
}
|
|
if( !token.Icmp( "move" ) )
|
|
{
|
|
deform = DFRM_MOVE;
|
|
deformRegisters[0] = ParseExpression( src );
|
|
return;
|
|
}
|
|
if( !token.Icmp( "turbulent" ) )
|
|
{
|
|
deform = DFRM_TURB;
|
|
|
|
if( !src.ExpectAnyToken( &token ) )
|
|
{
|
|
src.Warning( "deform particle missing particle name" );
|
|
SetMaterialFlag( MF_DEFAULTED );
|
|
return;
|
|
}
|
|
deformDecl = declManager->FindType( DECL_TABLE, token.c_str(), true );
|
|
|
|
deformRegisters[0] = ParseExpression( src );
|
|
deformRegisters[1] = ParseExpression( src );
|
|
deformRegisters[2] = ParseExpression( src );
|
|
return;
|
|
}
|
|
if( !token.Icmp( "eyeBall" ) )
|
|
{
|
|
deform = DFRM_EYEBALL;
|
|
return;
|
|
}
|
|
if( !token.Icmp( "particle" ) )
|
|
{
|
|
deform = DFRM_PARTICLE;
|
|
if( !src.ExpectAnyToken( &token ) )
|
|
{
|
|
src.Warning( "deform particle missing particle name" );
|
|
SetMaterialFlag( MF_DEFAULTED );
|
|
return;
|
|
}
|
|
deformDecl = declManager->FindType( DECL_PARTICLE, token.c_str(), true );
|
|
return;
|
|
}
|
|
if( !token.Icmp( "particle2" ) )
|
|
{
|
|
deform = DFRM_PARTICLE2;
|
|
if( !src.ExpectAnyToken( &token ) )
|
|
{
|
|
src.Warning( "deform particle missing particle name" );
|
|
SetMaterialFlag( MF_DEFAULTED );
|
|
return;
|
|
}
|
|
deformDecl = declManager->FindType( DECL_PARTICLE, token.c_str(), true );
|
|
return;
|
|
}
|
|
src.Warning( "Bad deform type '%s'", token.c_str() );
|
|
SetMaterialFlag( MF_DEFAULTED );
|
|
}
|
|
|
|
|
|
/*
|
|
==============
|
|
idMaterial::AddImplicitStages
|
|
|
|
If a material has diffuse or specular stages without any
|
|
bump stage, add an implicit _flat bumpmap stage.
|
|
|
|
If a material has a bump stage but no diffuse or specular
|
|
stage, add a _white diffuse stage.
|
|
|
|
It is valid to have either a diffuse or specular without the other.
|
|
|
|
It is valid to have a reflection map and a bump map for bumpy reflection
|
|
==============
|
|
*/
|
|
void idMaterial::AddImplicitStages( const textureRepeat_t trpDefault /* = TR_REPEAT */ )
|
|
{
|
|
char buffer[1024];
|
|
idLexer newSrc;
|
|
bool hasDiffuse = false;
|
|
bool hasSpecular = false;
|
|
bool hasBump = false;
|
|
bool hasReflection = false;
|
|
|
|
for( int i = 0 ; i < numStages ; i++ )
|
|
{
|
|
if( pd->parseStages[i].lighting == SL_BUMP )
|
|
{
|
|
hasBump = true;
|
|
}
|
|
if( pd->parseStages[i].lighting == SL_DIFFUSE )
|
|
{
|
|
hasDiffuse = true;
|
|
}
|
|
if( pd->parseStages[i].lighting == SL_SPECULAR )
|
|
{
|
|
hasSpecular = true;
|
|
}
|
|
if( pd->parseStages[i].texture.texgen == TG_REFLECT_CUBE )
|
|
{
|
|
hasReflection = true;
|
|
}
|
|
}
|
|
|
|
// if it doesn't have an interaction at all, don't add anything
|
|
if( !hasBump && !hasDiffuse && !hasSpecular )
|
|
{
|
|
return;
|
|
}
|
|
|
|
if( numStages == MAX_SHADER_STAGES )
|
|
{
|
|
return;
|
|
}
|
|
|
|
if( !hasBump )
|
|
{
|
|
idStr::snPrintf( buffer, sizeof( buffer ), "blend bumpmap\nmap _flat\n}\n" );
|
|
newSrc.LoadMemory( buffer, strlen( buffer ), "bumpmap" );
|
|
newSrc.SetFlags( LEXFL_NOFATALERRORS | LEXFL_NOSTRINGCONCAT | LEXFL_NOSTRINGESCAPECHARS | LEXFL_ALLOWPATHNAMES );
|
|
ParseStage( newSrc, trpDefault );
|
|
newSrc.FreeSource();
|
|
}
|
|
|
|
if( !hasDiffuse && !hasSpecular && !hasReflection )
|
|
{
|
|
idStr::snPrintf( buffer, sizeof( buffer ), "blend diffusemap\nmap _white\n}\n" );
|
|
newSrc.LoadMemory( buffer, strlen( buffer ), "diffusemap" );
|
|
newSrc.SetFlags( LEXFL_NOFATALERRORS | LEXFL_NOSTRINGCONCAT | LEXFL_NOSTRINGESCAPECHARS | LEXFL_ALLOWPATHNAMES );
|
|
ParseStage( newSrc, trpDefault );
|
|
newSrc.FreeSource();
|
|
}
|
|
|
|
}
|
|
|
|
/*
|
|
===============
|
|
idMaterial::SortInteractionStages
|
|
|
|
The renderer expects bump, then diffuse, then specular
|
|
There can be multiple bump maps, followed by additional
|
|
diffuse and specular stages, which allows cross-faded bump mapping.
|
|
|
|
Ambient stages can be interspersed anywhere, but they are
|
|
ignored during interactions, and all the interaction
|
|
stages are ignored during ambient drawing.
|
|
===============
|
|
*/
|
|
void idMaterial::SortInteractionStages()
|
|
{
|
|
int j;
|
|
|
|
for( int i = 0 ; i < numStages ; i = j )
|
|
{
|
|
// find the next bump map
|
|
for( j = i + 1 ; j < numStages ; j++ )
|
|
{
|
|
if( pd->parseStages[j].lighting == SL_BUMP )
|
|
{
|
|
// if the very first stage wasn't a bumpmap,
|
|
// this bumpmap is part of the first group
|
|
if( pd->parseStages[i].lighting != SL_BUMP )
|
|
{
|
|
continue;
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
// bubble sort everything bump / diffuse / specular
|
|
for( int l = 1 ; l < j - i ; l++ )
|
|
{
|
|
for( int k = i ; k < j - l ; k++ )
|
|
{
|
|
if( pd->parseStages[k].lighting > pd->parseStages[k + 1].lighting )
|
|
{
|
|
shaderStage_t temp;
|
|
|
|
temp = pd->parseStages[k];
|
|
pd->parseStages[k] = pd->parseStages[k + 1];
|
|
pd->parseStages[k + 1] = temp;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/*
|
|
=================
|
|
idMaterial::ParseMaterial
|
|
|
|
The current text pointer is at the explicit text definition of the
|
|
Parse it into the global material variable. Later functions will optimize it.
|
|
|
|
If there is any error during parsing, defaultShader will be set.
|
|
=================
|
|
*/
|
|
void idMaterial::ParseMaterial( idLexer& src )
|
|
{
|
|
idToken token;
|
|
int s;
|
|
char buffer[1024];
|
|
const char* str;
|
|
idLexer newSrc;
|
|
int i;
|
|
|
|
s = 0;
|
|
|
|
numOps = 0;
|
|
numRegisters = EXP_REG_NUM_PREDEFINED; // leave space for the parms to be copied in
|
|
for( i = 0 ; i < numRegisters ; i++ )
|
|
{
|
|
pd->registerIsTemporary[i] = true; // they aren't constants that can be folded
|
|
}
|
|
|
|
numStages = 0;
|
|
pd->registersAreConstant = true; // until shown otherwise
|
|
textureRepeat_t trpDefault = TR_REPEAT; // allow a global setting for repeat
|
|
|
|
while( 1 )
|
|
{
|
|
if( TestMaterialFlag( MF_DEFAULTED ) ) // we have a parse error
|
|
{
|
|
return;
|
|
}
|
|
if( !src.ExpectAnyToken( &token ) )
|
|
{
|
|
SetMaterialFlag( MF_DEFAULTED );
|
|
return;
|
|
}
|
|
|
|
// end of material definition
|
|
if( token == "}" )
|
|
{
|
|
break;
|
|
}
|
|
else if( !token.Icmp( "qer_editorimage" ) )
|
|
{
|
|
src.ReadTokenOnLine( &token );
|
|
editorImageName = token.c_str();
|
|
src.SkipRestOfLine();
|
|
continue;
|
|
}
|
|
// description
|
|
else if( !token.Icmp( "description" ) )
|
|
{
|
|
src.ReadTokenOnLine( &token );
|
|
desc = token.c_str();
|
|
continue;
|
|
}
|
|
// check for the surface / content bit flags
|
|
else if( CheckSurfaceParm( &token ) )
|
|
{
|
|
continue;
|
|
}
|
|
|
|
|
|
// polygonOffset
|
|
else if( !token.Icmp( "polygonOffset" ) )
|
|
{
|
|
SetMaterialFlag( MF_POLYGONOFFSET );
|
|
if( !src.ReadTokenOnLine( &token ) )
|
|
{
|
|
polygonOffset = 1;
|
|
continue;
|
|
}
|
|
// explict larger (or negative) offset
|
|
polygonOffset = token.GetFloatValue();
|
|
continue;
|
|
}
|
|
// noshadow
|
|
else if( !token.Icmp( "noShadows" ) )
|
|
{
|
|
SetMaterialFlag( MF_NOSHADOWS );
|
|
continue;
|
|
}
|
|
else if( !token.Icmp( "suppressInSubview" ) )
|
|
{
|
|
suppressInSubview = true;
|
|
continue;
|
|
}
|
|
else if( !token.Icmp( "portalSky" ) )
|
|
{
|
|
portalSky = true;
|
|
continue;
|
|
}
|
|
// noSelfShadow
|
|
else if( !token.Icmp( "noSelfShadow" ) )
|
|
{
|
|
SetMaterialFlag( MF_NOSELFSHADOW );
|
|
continue;
|
|
}
|
|
// noPortalFog
|
|
else if( !token.Icmp( "noPortalFog" ) )
|
|
{
|
|
SetMaterialFlag( MF_NOPORTALFOG );
|
|
continue;
|
|
}
|
|
// forceShadows allows nodraw surfaces to cast shadows
|
|
else if( !token.Icmp( "forceShadows" ) )
|
|
{
|
|
SetMaterialFlag( MF_FORCESHADOWS );
|
|
continue;
|
|
}
|
|
// overlay / decal suppression
|
|
else if( !token.Icmp( "noOverlays" ) )
|
|
{
|
|
allowOverlays = false;
|
|
continue;
|
|
}
|
|
// moster blood overlay forcing for alpha tested or translucent surfaces
|
|
else if( !token.Icmp( "forceOverlays" ) )
|
|
{
|
|
pd->forceOverlays = true;
|
|
continue;
|
|
}
|
|
// translucent
|
|
else if( !token.Icmp( "translucent" ) )
|
|
{
|
|
coverage = MC_TRANSLUCENT;
|
|
continue;
|
|
}
|
|
// global zero clamp
|
|
else if( !token.Icmp( "zeroclamp" ) )
|
|
{
|
|
trpDefault = TR_CLAMP_TO_ZERO;
|
|
continue;
|
|
}
|
|
// global clamp
|
|
else if( !token.Icmp( "clamp" ) )
|
|
{
|
|
trpDefault = TR_CLAMP;
|
|
continue;
|
|
}
|
|
// global clamp
|
|
else if( !token.Icmp( "alphazeroclamp" ) )
|
|
{
|
|
trpDefault = TR_CLAMP_TO_ZERO;
|
|
continue;
|
|
}
|
|
// forceOpaque is used for skies-behind-windows
|
|
else if( !token.Icmp( "forceOpaque" ) )
|
|
{
|
|
coverage = MC_OPAQUE;
|
|
continue;
|
|
}
|
|
// twoSided
|
|
else if( !token.Icmp( "twoSided" ) )
|
|
{
|
|
cullType = CT_TWO_SIDED;
|
|
// twoSided implies no-shadows, because the shadow
|
|
// volume would be coplanar with the surface, giving depth fighting
|
|
// we could make this no-self-shadows, but it may be more important
|
|
// to receive shadows from no-self-shadow monsters
|
|
SetMaterialFlag( MF_NOSHADOWS );
|
|
}
|
|
// backSided
|
|
else if( !token.Icmp( "backSided" ) )
|
|
{
|
|
cullType = CT_BACK_SIDED;
|
|
// the shadow code doesn't handle this, so just disable shadows.
|
|
// We could fix this in the future if there was a need.
|
|
SetMaterialFlag( MF_NOSHADOWS );
|
|
}
|
|
// foglight
|
|
else if( !token.Icmp( "fogLight" ) )
|
|
{
|
|
fogLight = true;
|
|
continue;
|
|
}
|
|
// blendlight
|
|
else if( !token.Icmp( "blendLight" ) )
|
|
{
|
|
blendLight = true;
|
|
continue;
|
|
}
|
|
// ambientLight
|
|
else if( !token.Icmp( "ambientLight" ) )
|
|
{
|
|
ambientLight = true;
|
|
continue;
|
|
}
|
|
// mirror
|
|
else if( !token.Icmp( "mirror" ) )
|
|
{
|
|
sort = SS_SUBVIEW;
|
|
coverage = MC_OPAQUE;
|
|
continue;
|
|
}
|
|
// noFog
|
|
else if( !token.Icmp( "noFog" ) )
|
|
{
|
|
noFog = true;
|
|
continue;
|
|
}
|
|
// unsmoothedTangents
|
|
else if( !token.Icmp( "unsmoothedTangents" ) )
|
|
{
|
|
unsmoothedTangents = true;
|
|
continue;
|
|
}
|
|
// lightFallofImage <imageprogram>
|
|
// specifies the image to use for the third axis of projected
|
|
// light volumes
|
|
else if( !token.Icmp( "lightFalloffImage" ) )
|
|
{
|
|
str = R_ParsePastImageProgram( src );
|
|
idStr copy;
|
|
|
|
copy = str; // so other things don't step on it
|
|
lightFalloffImage = globalImages->ImageFromFile( copy, TF_DEFAULT, TR_CLAMP /* TR_CLAMP_TO_ZERO */, TD_DEFAULT );
|
|
continue;
|
|
}
|
|
// guisurf <guifile> | guisurf entity
|
|
// an entity guisurf must have an idUserInterface
|
|
// specified in the renderEntity
|
|
else if( !token.Icmp( "guisurf" ) )
|
|
{
|
|
src.ReadTokenOnLine( &token );
|
|
if( !token.Icmp( "entity" ) )
|
|
{
|
|
entityGui = 1;
|
|
}
|
|
else if( !token.Icmp( "entity2" ) )
|
|
{
|
|
entityGui = 2;
|
|
}
|
|
else if( !token.Icmp( "entity3" ) )
|
|
{
|
|
entityGui = 3;
|
|
}
|
|
else
|
|
{
|
|
gui = uiManager->FindGui( token.c_str(), true );
|
|
}
|
|
continue;
|
|
}
|
|
// sort
|
|
else if( !token.Icmp( "sort" ) )
|
|
{
|
|
ParseSort( src );
|
|
continue;
|
|
}
|
|
else if( !token.Icmp( "stereoeye" ) )
|
|
{
|
|
ParseStereoEye( src );
|
|
continue;
|
|
}
|
|
// spectrum <integer>
|
|
else if( !token.Icmp( "spectrum" ) )
|
|
{
|
|
src.ReadTokenOnLine( &token );
|
|
spectrum = atoi( token.c_str() );
|
|
continue;
|
|
}
|
|
// deform < sprite | tube | flare >
|
|
else if( !token.Icmp( "deform" ) )
|
|
{
|
|
ParseDeform( src );
|
|
continue;
|
|
}
|
|
// decalInfo <staySeconds> <fadeSeconds> ( <start rgb> ) ( <end rgb> )
|
|
else if( !token.Icmp( "decalInfo" ) )
|
|
{
|
|
ParseDecalInfo( src );
|
|
continue;
|
|
}
|
|
// renderbump <args...>
|
|
else if( !token.Icmp( "renderbump" ) )
|
|
{
|
|
src.ParseRestOfLine( renderBump );
|
|
continue;
|
|
}
|
|
// diffusemap for stage shortcut
|
|
else if( !token.Icmp( "diffusemap" ) )
|
|
{
|
|
str = R_ParsePastImageProgram( src );
|
|
idStr::snPrintf( buffer, sizeof( buffer ), "blend diffusemap\nmap %s\n}\n", str );
|
|
newSrc.LoadMemory( buffer, strlen( buffer ), "diffusemap" );
|
|
newSrc.SetFlags( LEXFL_NOFATALERRORS | LEXFL_NOSTRINGCONCAT | LEXFL_NOSTRINGESCAPECHARS | LEXFL_ALLOWPATHNAMES );
|
|
ParseStage( newSrc, trpDefault );
|
|
newSrc.FreeSource();
|
|
continue;
|
|
}
|
|
// specularmap for stage shortcut
|
|
else if( !token.Icmp( "specularmap" ) )
|
|
{
|
|
str = R_ParsePastImageProgram( src );
|
|
idStr::snPrintf( buffer, sizeof( buffer ), "blend specularmap\nmap %s\n}\n", str );
|
|
newSrc.LoadMemory( buffer, strlen( buffer ), "specularmap" );
|
|
newSrc.SetFlags( LEXFL_NOFATALERRORS | LEXFL_NOSTRINGCONCAT | LEXFL_NOSTRINGESCAPECHARS | LEXFL_ALLOWPATHNAMES );
|
|
ParseStage( newSrc, trpDefault );
|
|
newSrc.FreeSource();
|
|
continue;
|
|
}
|
|
// normalmap for stage shortcut
|
|
else if( !token.Icmp( "bumpmap" ) )
|
|
{
|
|
str = R_ParsePastImageProgram( src );
|
|
idStr::snPrintf( buffer, sizeof( buffer ), "blend bumpmap\nmap %s\n}\n", str );
|
|
newSrc.LoadMemory( buffer, strlen( buffer ), "bumpmap" );
|
|
newSrc.SetFlags( LEXFL_NOFATALERRORS | LEXFL_NOSTRINGCONCAT | LEXFL_NOSTRINGESCAPECHARS | LEXFL_ALLOWPATHNAMES );
|
|
ParseStage( newSrc, trpDefault );
|
|
newSrc.FreeSource();
|
|
continue;
|
|
}
|
|
// DECAL_MACRO for backwards compatibility with the preprocessor macros
|
|
else if( !token.Icmp( "DECAL_MACRO" ) )
|
|
{
|
|
// polygonOffset
|
|
SetMaterialFlag( MF_POLYGONOFFSET );
|
|
polygonOffset = 1;
|
|
|
|
// discrete
|
|
surfaceFlags |= SURF_DISCRETE;
|
|
contentFlags &= ~CONTENTS_SOLID;
|
|
|
|
// sort decal
|
|
sort = SS_DECAL;
|
|
|
|
// noShadows
|
|
SetMaterialFlag( MF_NOSHADOWS );
|
|
continue;
|
|
}
|
|
else if( token == "{" )
|
|
{
|
|
// create the new stage
|
|
ParseStage( src, trpDefault );
|
|
continue;
|
|
}
|
|
else
|
|
{
|
|
common->Warning( "unknown general material parameter '%s' in '%s'", token.c_str(), GetName() );
|
|
SetMaterialFlag( MF_DEFAULTED );
|
|
return;
|
|
}
|
|
}
|
|
|
|
// add _flat or _white stages if needed
|
|
AddImplicitStages();
|
|
|
|
// order the diffuse / bump / specular stages properly
|
|
SortInteractionStages();
|
|
|
|
// if we need to do anything with normals (lighting or environment mapping)
|
|
// and two sided lighting was asked for, flag
|
|
// shouldCreateBackSides() and change culling back to single sided,
|
|
// so we get proper tangent vectors on both sides
|
|
|
|
// we can't just call ReceivesLighting(), because the stages are still
|
|
// in temporary form
|
|
if( cullType == CT_TWO_SIDED )
|
|
{
|
|
for( i = 0 ; i < numStages ; i++ )
|
|
{
|
|
if( pd->parseStages[i].lighting != SL_AMBIENT || pd->parseStages[i].texture.texgen != TG_EXPLICIT )
|
|
{
|
|
if( cullType == CT_TWO_SIDED )
|
|
{
|
|
cullType = CT_FRONT_SIDED;
|
|
shouldCreateBackSides = true;
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
// currently a surface can only have one unique texgen for all the stages on old hardware
|
|
texgen_t firstGen = TG_EXPLICIT;
|
|
for( i = 0; i < numStages; i++ )
|
|
{
|
|
if( pd->parseStages[i].texture.texgen != TG_EXPLICIT )
|
|
{
|
|
if( firstGen == TG_EXPLICIT )
|
|
{
|
|
firstGen = pd->parseStages[i].texture.texgen;
|
|
}
|
|
else if( firstGen != pd->parseStages[i].texture.texgen )
|
|
{
|
|
common->Warning( "material '%s' has multiple stages with a texgen", GetName() );
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/*
|
|
=========================
|
|
idMaterial::SetGui
|
|
=========================
|
|
*/
|
|
void idMaterial::SetGui( const char* _gui ) const
|
|
{
|
|
gui = uiManager->FindGui( _gui, true, false, true );
|
|
}
|
|
|
|
/*
|
|
=========================
|
|
idMaterial::Parse
|
|
|
|
Parses the current material definition and finds all necessary images.
|
|
=========================
|
|
*/
|
|
bool idMaterial::Parse( const char* text, const int textLength, bool allowBinaryVersion )
|
|
{
|
|
idLexer src;
|
|
idToken token;
|
|
mtrParsingData_t parsingData;
|
|
|
|
src.LoadMemory( text, textLength, GetFileName(), GetLineNum() );
|
|
src.SetFlags( DECL_LEXER_FLAGS );
|
|
src.SkipUntilString( "{" );
|
|
|
|
// reset to the unparsed state
|
|
CommonInit();
|
|
|
|
memset( &parsingData, 0, sizeof( parsingData ) );
|
|
|
|
pd = &parsingData; // this is only valid during parse
|
|
|
|
// parse it
|
|
ParseMaterial( src );
|
|
|
|
// if we are doing an fs_copyfiles, also reference the editorImage
|
|
if( cvarSystem->GetCVarInteger( "fs_copyFiles" ) )
|
|
{
|
|
GetEditorImage();
|
|
}
|
|
|
|
//
|
|
// count non-lit stages
|
|
numAmbientStages = 0;
|
|
int i;
|
|
for( i = 0 ; i < numStages ; i++ )
|
|
{
|
|
if( pd->parseStages[i].lighting == SL_AMBIENT )
|
|
{
|
|
numAmbientStages++;
|
|
}
|
|
}
|
|
|
|
// see if there is a subview stage
|
|
if( sort == SS_SUBVIEW )
|
|
{
|
|
hasSubview = true;
|
|
}
|
|
else
|
|
{
|
|
hasSubview = false;
|
|
for( i = 0 ; i < numStages ; i++ )
|
|
{
|
|
if( pd->parseStages[i].texture.dynamic )
|
|
{
|
|
hasSubview = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
// automatically determine coverage if not explicitly set
|
|
if( coverage == MC_BAD )
|
|
{
|
|
// automatically set MC_TRANSLUCENT if we don't have any interaction stages and
|
|
// the first stage is blended and not an alpha test mask or a subview
|
|
if( !numStages )
|
|
{
|
|
// non-visible
|
|
coverage = MC_TRANSLUCENT;
|
|
}
|
|
else if( numStages != numAmbientStages )
|
|
{
|
|
// we have an interaction draw
|
|
coverage = MC_OPAQUE;
|
|
}
|
|
else if(
|
|
( pd->parseStages[0].drawStateBits & GLS_DSTBLEND_BITS ) != GLS_DSTBLEND_ZERO ||
|
|
( pd->parseStages[0].drawStateBits & GLS_SRCBLEND_BITS ) == GLS_SRCBLEND_DST_COLOR ||
|
|
( pd->parseStages[0].drawStateBits & GLS_SRCBLEND_BITS ) == GLS_SRCBLEND_ONE_MINUS_DST_COLOR ||
|
|
( pd->parseStages[0].drawStateBits & GLS_SRCBLEND_BITS ) == GLS_SRCBLEND_DST_ALPHA ||
|
|
( pd->parseStages[0].drawStateBits & GLS_SRCBLEND_BITS ) == GLS_SRCBLEND_ONE_MINUS_DST_ALPHA
|
|
)
|
|
{
|
|
// blended with the destination
|
|
coverage = MC_TRANSLUCENT;
|
|
}
|
|
else
|
|
{
|
|
coverage = MC_OPAQUE;
|
|
}
|
|
}
|
|
|
|
// translucent automatically implies noshadows
|
|
if( coverage == MC_TRANSLUCENT )
|
|
{
|
|
SetMaterialFlag( MF_NOSHADOWS );
|
|
}
|
|
else
|
|
{
|
|
// mark the contents as opaque
|
|
contentFlags |= CONTENTS_OPAQUE;
|
|
}
|
|
|
|
// if we are translucent, draw with an alpha in the editor
|
|
if( coverage == MC_TRANSLUCENT )
|
|
{
|
|
editorAlpha = 0.5;
|
|
}
|
|
else
|
|
{
|
|
editorAlpha = 1.0;
|
|
}
|
|
|
|
// the sorts can make reasonable defaults
|
|
if( sort == SS_BAD )
|
|
{
|
|
if( TestMaterialFlag( MF_POLYGONOFFSET ) )
|
|
{
|
|
sort = SS_DECAL;
|
|
}
|
|
else if( coverage == MC_TRANSLUCENT )
|
|
{
|
|
sort = SS_MEDIUM;
|
|
}
|
|
else
|
|
{
|
|
sort = SS_OPAQUE;
|
|
}
|
|
}
|
|
|
|
// anything that references _currentRender will automatically get sort = SS_POST_PROCESS
|
|
// and coverage = MC_TRANSLUCENT
|
|
|
|
for( i = 0 ; i < numStages ; i++ )
|
|
{
|
|
shaderStage_t* pStage = &pd->parseStages[i];
|
|
if( pStage->texture.image == globalImages->originalCurrentRenderImage )
|
|
{
|
|
if( sort != SS_PORTAL_SKY )
|
|
{
|
|
sort = SS_POST_PROCESS;
|
|
coverage = MC_TRANSLUCENT;
|
|
}
|
|
break;
|
|
}
|
|
if( pStage->newStage )
|
|
{
|
|
for( int j = 0 ; j < pStage->newStage->numFragmentProgramImages ; j++ )
|
|
{
|
|
if( pStage->newStage->fragmentProgramImages[j] == globalImages->originalCurrentRenderImage )
|
|
{
|
|
if( sort != SS_PORTAL_SKY )
|
|
{
|
|
sort = SS_POST_PROCESS;
|
|
coverage = MC_TRANSLUCENT;
|
|
}
|
|
i = numStages;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// set the drawStateBits depth flags
|
|
for( i = 0 ; i < numStages ; i++ )
|
|
{
|
|
shaderStage_t* pStage = &pd->parseStages[i];
|
|
if( sort == SS_POST_PROCESS )
|
|
{
|
|
// post-process effects fill the depth buffer as they draw, so only the
|
|
// topmost post-process effect is rendered
|
|
pStage->drawStateBits |= GLS_DEPTHFUNC_LESS;
|
|
}
|
|
else if( coverage == MC_TRANSLUCENT || pStage->ignoreAlphaTest )
|
|
{
|
|
// translucent surfaces can extend past the exactly marked depth buffer
|
|
pStage->drawStateBits |= GLS_DEPTHFUNC_LESS | GLS_DEPTHMASK;
|
|
}
|
|
else
|
|
{
|
|
// opaque and perforated surfaces must exactly match the depth buffer,
|
|
// which gets alpha test correct
|
|
pStage->drawStateBits |= GLS_DEPTHFUNC_EQUAL | GLS_DEPTHMASK;
|
|
}
|
|
}
|
|
|
|
// determine if this surface will accept overlays / decals
|
|
|
|
if( pd->forceOverlays )
|
|
{
|
|
// explicitly flaged in material definition
|
|
allowOverlays = true;
|
|
}
|
|
else
|
|
{
|
|
if( !IsDrawn() )
|
|
{
|
|
allowOverlays = false;
|
|
}
|
|
if( Coverage() != MC_OPAQUE )
|
|
{
|
|
allowOverlays = false;
|
|
}
|
|
if( GetSurfaceFlags() & SURF_NOIMPACT )
|
|
{
|
|
allowOverlays = false;
|
|
}
|
|
}
|
|
|
|
// add a tiny offset to the sort orders, so that different materials
|
|
// that have the same sort value will at least sort consistantly, instead
|
|
// of flickering back and forth
|
|
/* this messed up in-game guis
|
|
if ( sort != SS_SUBVIEW ) {
|
|
int hash, l;
|
|
|
|
l = name.Length();
|
|
hash = 0;
|
|
for ( int i = 0 ; i < l ; i++ ) {
|
|
hash ^= name[i];
|
|
}
|
|
sort += hash * 0.01;
|
|
}
|
|
*/
|
|
|
|
if( numStages )
|
|
{
|
|
stages = ( shaderStage_t* )R_StaticAlloc( numStages * sizeof( stages[0] ), TAG_MATERIAL );
|
|
memcpy( stages, pd->parseStages, numStages * sizeof( stages[0] ) );
|
|
}
|
|
|
|
if( numOps )
|
|
{
|
|
ops = ( expOp_t* )R_StaticAlloc( numOps * sizeof( ops[0] ), TAG_MATERIAL );
|
|
memcpy( ops, pd->shaderOps, numOps * sizeof( ops[0] ) );
|
|
}
|
|
|
|
if( numRegisters )
|
|
{
|
|
expressionRegisters = ( float* )R_StaticAlloc( numRegisters * sizeof( expressionRegisters[0] ), TAG_MATERIAL );
|
|
memcpy( expressionRegisters, pd->shaderRegisters, numRegisters * sizeof( expressionRegisters[0] ) );
|
|
}
|
|
|
|
// see if the registers are completely constant, and don't need to be evaluated
|
|
// per-surface
|
|
CheckForConstantRegisters();
|
|
|
|
// See if the material is trivial for the fast path
|
|
SetFastPathImages();
|
|
|
|
pd = NULL; // the pointer will be invalid after exiting this function
|
|
|
|
// finish things up
|
|
if( TestMaterialFlag( MF_DEFAULTED ) )
|
|
{
|
|
MakeDefault();
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
/*
|
|
===================
|
|
idMaterial::Print
|
|
===================
|
|
*/
|
|
const char* opNames[] =
|
|
{
|
|
"OP_TYPE_ADD",
|
|
"OP_TYPE_SUBTRACT",
|
|
"OP_TYPE_MULTIPLY",
|
|
"OP_TYPE_DIVIDE",
|
|
"OP_TYPE_MOD",
|
|
"OP_TYPE_TABLE",
|
|
"OP_TYPE_GT",
|
|
"OP_TYPE_GE",
|
|
"OP_TYPE_LT",
|
|
"OP_TYPE_LE",
|
|
"OP_TYPE_EQ",
|
|
"OP_TYPE_NE",
|
|
"OP_TYPE_AND",
|
|
"OP_TYPE_OR"
|
|
};
|
|
|
|
void idMaterial::Print() const
|
|
{
|
|
int i;
|
|
|
|
for( i = EXP_REG_NUM_PREDEFINED ; i < GetNumRegisters() ; i++ )
|
|
{
|
|
common->Printf( "register %i: %f\n", i, expressionRegisters[i] );
|
|
}
|
|
common->Printf( "\n" );
|
|
for( i = 0 ; i < numOps ; i++ )
|
|
{
|
|
const expOp_t* op = &ops[i];
|
|
if( op->opType == OP_TYPE_TABLE )
|
|
{
|
|
common->Printf( "%i = %s[ %i ]\n", op->c, declManager->DeclByIndex( DECL_TABLE, op->a )->GetName(), op->b );
|
|
}
|
|
else
|
|
{
|
|
common->Printf( "%i = %i %s %i\n", op->c, op->a, opNames[ op->opType ], op->b );
|
|
}
|
|
}
|
|
}
|
|
|
|
/*
|
|
===============
|
|
idMaterial::Save
|
|
===============
|
|
*/
|
|
bool idMaterial::Save( const char* fileName )
|
|
{
|
|
return ReplaceSourceFileText();
|
|
}
|
|
|
|
/*
|
|
===============
|
|
idMaterial::AddReference
|
|
===============
|
|
*/
|
|
void idMaterial::AddReference()
|
|
{
|
|
refCount++;
|
|
|
|
for( int i = 0; i < numStages; i++ )
|
|
{
|
|
shaderStage_t* s = &stages[i];
|
|
|
|
if( s->texture.image )
|
|
{
|
|
s->texture.image->AddReference();
|
|
}
|
|
}
|
|
}
|
|
|
|
/*
|
|
===============
|
|
idMaterial::EvaluateRegisters
|
|
|
|
Parameters are taken from the localSpace and the renderView,
|
|
then all expressions are evaluated, leaving the material registers
|
|
set to their apropriate values.
|
|
===============
|
|
*/
|
|
void idMaterial::EvaluateRegisters(
|
|
float* registers,
|
|
const float localShaderParms[MAX_ENTITY_SHADER_PARMS],
|
|
const float globalShaderParms[MAX_GLOBAL_SHADER_PARMS],
|
|
const float floatTime,
|
|
idSoundEmitter* soundEmitter ) const
|
|
{
|
|
|
|
int i, b;
|
|
expOp_t* op;
|
|
|
|
// copy the material constants
|
|
for( i = EXP_REG_NUM_PREDEFINED ; i < numRegisters ; i++ )
|
|
{
|
|
registers[i] = expressionRegisters[i];
|
|
}
|
|
|
|
// copy the local and global parameters
|
|
registers[EXP_REG_TIME] = floatTime;
|
|
registers[EXP_REG_PARM0] = localShaderParms[0];
|
|
registers[EXP_REG_PARM1] = localShaderParms[1];
|
|
registers[EXP_REG_PARM2] = localShaderParms[2];
|
|
registers[EXP_REG_PARM3] = localShaderParms[3];
|
|
registers[EXP_REG_PARM4] = localShaderParms[4];
|
|
registers[EXP_REG_PARM5] = localShaderParms[5];
|
|
registers[EXP_REG_PARM6] = localShaderParms[6];
|
|
registers[EXP_REG_PARM7] = localShaderParms[7];
|
|
registers[EXP_REG_PARM8] = localShaderParms[8];
|
|
registers[EXP_REG_PARM9] = localShaderParms[9];
|
|
registers[EXP_REG_PARM10] = localShaderParms[10];
|
|
registers[EXP_REG_PARM11] = localShaderParms[11];
|
|
registers[EXP_REG_GLOBAL0] = globalShaderParms[0];
|
|
registers[EXP_REG_GLOBAL1] = globalShaderParms[1];
|
|
registers[EXP_REG_GLOBAL2] = globalShaderParms[2];
|
|
registers[EXP_REG_GLOBAL3] = globalShaderParms[3];
|
|
registers[EXP_REG_GLOBAL4] = globalShaderParms[4];
|
|
registers[EXP_REG_GLOBAL5] = globalShaderParms[5];
|
|
registers[EXP_REG_GLOBAL6] = globalShaderParms[6];
|
|
registers[EXP_REG_GLOBAL7] = globalShaderParms[7];
|
|
|
|
op = ops;
|
|
for( i = 0 ; i < numOps ; i++, op++ )
|
|
{
|
|
switch( op->opType )
|
|
{
|
|
case OP_TYPE_ADD:
|
|
registers[op->c] = registers[op->a] + registers[op->b];
|
|
break;
|
|
case OP_TYPE_SUBTRACT:
|
|
registers[op->c] = registers[op->a] - registers[op->b];
|
|
break;
|
|
case OP_TYPE_MULTIPLY:
|
|
registers[op->c] = registers[op->a] * registers[op->b];
|
|
break;
|
|
case OP_TYPE_DIVIDE:
|
|
registers[op->c] = registers[op->a] / registers[op->b];
|
|
break;
|
|
case OP_TYPE_MOD:
|
|
b = ( int )registers[op->b];
|
|
b = b != 0 ? b : 1;
|
|
registers[op->c] = ( int )registers[op->a] % b;
|
|
break;
|
|
case OP_TYPE_TABLE:
|
|
{
|
|
const idDeclTable* table = static_cast<const idDeclTable*>( declManager->DeclByIndex( DECL_TABLE, op->a ) );
|
|
registers[op->c] = table->TableLookup( registers[op->b] );
|
|
}
|
|
break;
|
|
case OP_TYPE_SOUND:
|
|
if( r_forceSoundOpAmplitude.GetFloat() > 0 )
|
|
{
|
|
registers[op->c] = r_forceSoundOpAmplitude.GetFloat();
|
|
}
|
|
else if( soundEmitter )
|
|
{
|
|
registers[op->c] = soundEmitter->CurrentAmplitude();
|
|
}
|
|
else
|
|
{
|
|
registers[op->c] = 0;
|
|
}
|
|
break;
|
|
case OP_TYPE_GT:
|
|
registers[op->c] = registers[ op->a ] > registers[op->b];
|
|
break;
|
|
case OP_TYPE_GE:
|
|
registers[op->c] = registers[ op->a ] >= registers[op->b];
|
|
break;
|
|
case OP_TYPE_LT:
|
|
registers[op->c] = registers[ op->a ] < registers[op->b];
|
|
break;
|
|
case OP_TYPE_LE:
|
|
registers[op->c] = registers[ op->a ] <= registers[op->b];
|
|
break;
|
|
case OP_TYPE_EQ:
|
|
registers[op->c] = registers[ op->a ] == registers[op->b];
|
|
break;
|
|
case OP_TYPE_NE:
|
|
registers[op->c] = registers[ op->a ] != registers[op->b];
|
|
break;
|
|
case OP_TYPE_AND:
|
|
registers[op->c] = registers[ op->a ] && registers[op->b];
|
|
break;
|
|
case OP_TYPE_OR:
|
|
registers[op->c] = registers[ op->a ] || registers[op->b];
|
|
break;
|
|
default:
|
|
common->FatalError( "R_EvaluateExpression: bad opcode" );
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
/*
|
|
=============
|
|
idMaterial::Texgen
|
|
=============
|
|
*/
|
|
texgen_t idMaterial::Texgen() const
|
|
{
|
|
if( stages )
|
|
{
|
|
for( int i = 0; i < numStages; i++ )
|
|
{
|
|
if( stages[ i ].texture.texgen != TG_EXPLICIT )
|
|
{
|
|
return stages[ i ].texture.texgen;
|
|
}
|
|
}
|
|
}
|
|
|
|
return TG_EXPLICIT;
|
|
}
|
|
|
|
/*
|
|
=============
|
|
idMaterial::GetImageWidth
|
|
=============
|
|
*/
|
|
int idMaterial::GetImageWidth() const
|
|
{
|
|
assert( GetStage( 0 ) && GetStage( 0 )->texture.image );
|
|
return GetStage( 0 )->texture.image->GetUploadWidth();
|
|
}
|
|
|
|
/*
|
|
=============
|
|
idMaterial::GetImageHeight
|
|
=============
|
|
*/
|
|
int idMaterial::GetImageHeight() const
|
|
{
|
|
assert( GetStage( 0 ) && GetStage( 0 )->texture.image );
|
|
return GetStage( 0 )->texture.image->GetUploadHeight();
|
|
}
|
|
|
|
/*
|
|
=============
|
|
idMaterial::CinematicLength
|
|
=============
|
|
*/
|
|
int idMaterial::CinematicLength() const
|
|
{
|
|
if( !stages || !stages[0].texture.cinematic )
|
|
{
|
|
return 0;
|
|
}
|
|
return stages[0].texture.cinematic->AnimationLength();
|
|
}
|
|
|
|
/*
|
|
=============
|
|
idMaterial::UpdateCinematic
|
|
=============
|
|
*/
|
|
void idMaterial::UpdateCinematic( int time ) const
|
|
{
|
|
}
|
|
|
|
/*
|
|
=============
|
|
idMaterial::CloseCinematic
|
|
=============
|
|
*/
|
|
void idMaterial::CloseCinematic() const
|
|
{
|
|
for( int i = 0; i < numStages; i++ )
|
|
{
|
|
if( stages[i].texture.cinematic )
|
|
{
|
|
stages[i].texture.cinematic->Close();
|
|
delete stages[i].texture.cinematic;
|
|
stages[i].texture.cinematic = NULL;
|
|
}
|
|
}
|
|
}
|
|
|
|
/*
|
|
=============
|
|
idMaterial::ResetCinematicTime
|
|
=============
|
|
*/
|
|
void idMaterial::ResetCinematicTime( int time ) const
|
|
{
|
|
for( int i = 0; i < numStages; i++ )
|
|
{
|
|
if( stages[i].texture.cinematic )
|
|
{
|
|
stages[i].texture.cinematic->ResetTime( time );
|
|
}
|
|
}
|
|
}
|
|
|
|
/*
|
|
=============
|
|
idMaterial::GetCinematicStartTime
|
|
=============
|
|
*/
|
|
int idMaterial::GetCinematicStartTime() const
|
|
{
|
|
for( int i = 0; i < numStages; i++ )
|
|
{
|
|
if( stages[i].texture.cinematic )
|
|
{
|
|
return stages[i].texture.cinematic->GetStartTime();
|
|
}
|
|
}
|
|
return -1;
|
|
}
|
|
|
|
// RB: added because we can't rely on the FFmpeg feedback how long a video really is
|
|
bool idMaterial::CinematicIsPlaying() const
|
|
{
|
|
if( !stages || !stages[0].texture.cinematic )
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
return stages[0].texture.cinematic->IsPlaying();
|
|
}
|
|
// RB end
|
|
|
|
/*
|
|
==================
|
|
idMaterial::CheckForConstantRegisters
|
|
|
|
As of 5/2/03, about half of the unique materials loaded on typical
|
|
maps are constant, but 2/3 of the surface references are.
|
|
==================
|
|
*/
|
|
void idMaterial::CheckForConstantRegisters()
|
|
{
|
|
assert( constantRegisters == NULL );
|
|
|
|
if( !pd->registersAreConstant )
|
|
{
|
|
return;
|
|
}
|
|
if( !r_useConstantMaterials.GetBool() )
|
|
{
|
|
return;
|
|
}
|
|
|
|
// evaluate the registers once, and save them
|
|
constantRegisters = ( float* )R_ClearedStaticAlloc( GetNumRegisters() * sizeof( float ) );
|
|
|
|
float shaderParms[MAX_ENTITY_SHADER_PARMS];
|
|
memset( shaderParms, 0, sizeof( shaderParms ) );
|
|
viewDef_t viewDef;
|
|
memset( &viewDef, 0, sizeof( viewDef ) );
|
|
|
|
EvaluateRegisters( constantRegisters, shaderParms, viewDef.renderView.shaderParms, 0.0f, 0 );
|
|
}
|
|
|
|
/*
|
|
===================
|
|
idMaterial::ImageName
|
|
===================
|
|
*/
|
|
const char* idMaterial::ImageName() const
|
|
{
|
|
if( numStages == 0 )
|
|
{
|
|
return "_scratch";
|
|
}
|
|
idImage* image = stages[0].texture.image;
|
|
if( image )
|
|
{
|
|
return image->GetName();
|
|
}
|
|
return "_scratch";
|
|
}
|
|
|
|
/*
|
|
=================
|
|
idMaterial::Size
|
|
=================
|
|
*/
|
|
size_t idMaterial::Size() const
|
|
{
|
|
return sizeof( idMaterial );
|
|
}
|
|
|
|
/*
|
|
===================
|
|
idMaterial::SetDefaultText
|
|
===================
|
|
*/
|
|
bool idMaterial::SetDefaultText()
|
|
{
|
|
// if there exists an image with the same name
|
|
if( 1 ) //fileSystem->ReadFile( GetName(), NULL ) != -1 ) {
|
|
{
|
|
char generated[2048];
|
|
idStr::snPrintf( generated, sizeof( generated ),
|
|
"material %s // IMPLICITLY GENERATED\n"
|
|
"{\n"
|
|
"{\n"
|
|
"blend blend\n"
|
|
"colored\n"
|
|
"map \"%s\"\n"
|
|
"clamp\n"
|
|
"}\n"
|
|
"}\n", GetName(), GetName() );
|
|
SetText( generated );
|
|
return true;
|
|
}
|
|
else
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
|
|
/*
|
|
===================
|
|
idMaterial::DefaultDefinition
|
|
===================
|
|
*/
|
|
const char* idMaterial::DefaultDefinition() const
|
|
{
|
|
return
|
|
"{\n"
|
|
"\t" "{\n"
|
|
"\t\t" "blend\tblend\n"
|
|
"\t\t" "map\t\t_default\n"
|
|
"\t" "}\n"
|
|
"}";
|
|
}
|
|
|
|
|
|
/*
|
|
===================
|
|
idMaterial::GetBumpStage
|
|
===================
|
|
*/
|
|
const shaderStage_t* idMaterial::GetBumpStage() const
|
|
{
|
|
for( int i = 0 ; i < numStages ; i++ )
|
|
{
|
|
if( stages[i].lighting == SL_BUMP )
|
|
{
|
|
return &stages[i];
|
|
}
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
/*
|
|
===================
|
|
idMaterial::ReloadImages
|
|
===================
|
|
*/
|
|
void idMaterial::ReloadImages( bool force ) const
|
|
{
|
|
for( int i = 0 ; i < numStages ; i++ )
|
|
{
|
|
if( stages[i].newStage )
|
|
{
|
|
for( int j = 0 ; j < stages[i].newStage->numFragmentProgramImages ; j++ )
|
|
{
|
|
if( stages[i].newStage->fragmentProgramImages[j] )
|
|
{
|
|
stages[i].newStage->fragmentProgramImages[j]->Reload( force );
|
|
}
|
|
}
|
|
}
|
|
else if( stages[i].texture.image )
|
|
{
|
|
stages[i].texture.image->Reload( force );
|
|
}
|
|
}
|
|
}
|
|
|
|
/*
|
|
=============
|
|
idMaterial::SetFastPathImages
|
|
|
|
See if the material is trivial for the fast path
|
|
=============
|
|
*/
|
|
void idMaterial::SetFastPathImages()
|
|
{
|
|
fastPathBumpImage = NULL;
|
|
fastPathDiffuseImage = NULL;
|
|
fastPathSpecularImage = NULL;
|
|
|
|
if( constantRegisters == NULL )
|
|
{
|
|
return;
|
|
}
|
|
|
|
// go through the individual surface stages
|
|
//
|
|
// We also have the very rare case of some materials that have conditional interactions
|
|
// for the "hell writing" that can be shined on them.
|
|
|
|
for( int surfaceStageNum = 0; surfaceStageNum < GetNumStages(); surfaceStageNum++ )
|
|
{
|
|
const shaderStage_t* surfaceStage = GetStage( surfaceStageNum );
|
|
|
|
if( surfaceStage->texture.hasMatrix )
|
|
{
|
|
goto fail;
|
|
}
|
|
|
|
// check for vertex coloring
|
|
if( surfaceStage->vertexColor != SVC_IGNORE )
|
|
{
|
|
goto fail;
|
|
}
|
|
|
|
// check for non-identity colors
|
|
for( int i = 0; i < 4; i++ )
|
|
{
|
|
if( idMath::Fabs( constantRegisters[surfaceStage->color.registers[i]] - 1.0f ) > 0.1f )
|
|
{
|
|
goto fail;
|
|
}
|
|
}
|
|
|
|
switch( surfaceStage->lighting )
|
|
{
|
|
case SL_COVERAGE:
|
|
case SL_AMBIENT:
|
|
break;
|
|
case SL_BUMP:
|
|
{
|
|
if( fastPathBumpImage )
|
|
{
|
|
goto fail;
|
|
}
|
|
fastPathBumpImage = surfaceStage->texture.image;
|
|
break;
|
|
}
|
|
case SL_DIFFUSE:
|
|
{
|
|
if( fastPathDiffuseImage )
|
|
{
|
|
goto fail;
|
|
}
|
|
fastPathDiffuseImage = surfaceStage->texture.image;
|
|
break;
|
|
}
|
|
case SL_SPECULAR:
|
|
{
|
|
if( fastPathSpecularImage )
|
|
{
|
|
goto fail;
|
|
}
|
|
fastPathSpecularImage = surfaceStage->texture.image;
|
|
}
|
|
}
|
|
}
|
|
// need a bump image, but specular can default
|
|
// we also need a diffuse image, because we can't get a pure black with our YCoCg conversion
|
|
// from 565 DXT. The general-path code also sets the diffuse color to 0 in the default case,
|
|
// but the fast path can't.
|
|
if( fastPathBumpImage == NULL || fastPathDiffuseImage == NULL )
|
|
{
|
|
goto fail;
|
|
}
|
|
if( fastPathSpecularImage == NULL )
|
|
{
|
|
fastPathSpecularImage = globalImages->blackImage;
|
|
}
|
|
return;
|
|
|
|
fail:
|
|
fastPathBumpImage = NULL;
|
|
fastPathDiffuseImage = NULL;
|
|
fastPathSpecularImage = NULL;
|
|
}
|