mirror of
https://github.com/jmarshall23/DoomRTX.git
synced 2026-08-12 16:21:04 +02:00
b6ea2fb5fa
Fixed numerous bugs with the alpha.
4253 lines
110 KiB
C++
4253 lines
110 KiB
C++
/*
|
|
===========================================================================
|
|
|
|
IceTech GPL Source Code
|
|
Copyright (C) 2026 Justin Marshall
|
|
|
|
This file is part of the IceTech GPL Source Code (?IceTech Source Code?).
|
|
|
|
IceTech 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.
|
|
|
|
IceTech 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 IceTech Source Code. If not, see <http://www.gnu.org/licenses/>.
|
|
|
|
In addition, the IceTech 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 IceTech 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 Justin Marshall, justinmarshall20@gmail.com
|
|
|
|
===========================================================================
|
|
*/
|
|
|
|
#include "precompiled.h"
|
|
#pragma hdrstop
|
|
|
|
#include "tr_local.h"
|
|
|
|
#ifdef QUAKE4
|
|
#include "../../quake4/idLib/Lib.h"
|
|
#endif
|
|
|
|
idCVar r_trainmats("r_trainmats", "0", CVAR_BOOL, "");
|
|
|
|
/*
|
|
|
|
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;
|
|
|
|
#ifdef QUAKE4
|
|
#ifndef GL_LESS
|
|
#define GL_LESS 0x0201
|
|
#endif
|
|
#ifndef GL_EQUAL
|
|
#define GL_EQUAL 0x0202
|
|
#endif
|
|
#ifndef GL_GREATER
|
|
#define GL_GREATER 0x0204
|
|
#endif
|
|
|
|
static int R_MaterialMakePowerOfTwo(int value) {
|
|
int pot = 1;
|
|
while (pot < value) {
|
|
pot <<= 1;
|
|
}
|
|
return pot;
|
|
}
|
|
#endif
|
|
|
|
|
|
/*
|
|
=============
|
|
idMaterial::CommonInit
|
|
=============
|
|
*/
|
|
void idMaterial::CommonInit() {
|
|
desc = "<none>";
|
|
renderBump = "";
|
|
#ifdef QUAKE4
|
|
portalDistanceNear = 262144.0f;
|
|
portalDistanceFar = 262144.0f;
|
|
materialType = NULL;
|
|
materialTypeArray = NULL;
|
|
materialTypeArrayName = "";
|
|
MTAWidth = 0;
|
|
MTAHeight = 0;
|
|
portalImage = NULL;
|
|
useCount = 0;
|
|
#endif
|
|
contentFlags = CONTENTS_SOLID;
|
|
surfaceFlags = SURFTYPE_NONE;
|
|
materialFlags = 0;
|
|
sort = SS_BAD;
|
|
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;
|
|
|
|
decalInfo.stayTime = 10000;
|
|
#ifdef QUAKE4
|
|
decalInfo.maxAngle = 0.1f;
|
|
#else
|
|
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;
|
|
#endif
|
|
|
|
#ifdef PREY
|
|
subviewClass = SC_MIRROR;
|
|
directPortalDistance = 0;
|
|
#endif
|
|
|
|
neuralPOMBytesLoaded = false;
|
|
neuralPOMHasData = false;
|
|
neuralPOMUploadedToShim = false;
|
|
neuralPOMGLHandle = 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;
|
|
#ifdef QUAKE4
|
|
globalUseCount = 0;
|
|
#endif
|
|
}
|
|
|
|
/*
|
|
=============
|
|
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;
|
|
}
|
|
#ifdef QUAKE4
|
|
if (stages[i].newShaderStage != NULL) {
|
|
delete stages[i].newShaderStage;
|
|
stages[i].newShaderStage = NULL;
|
|
}
|
|
#endif
|
|
}
|
|
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;
|
|
}
|
|
#ifdef QUAKE4
|
|
if (materialTypeArray != NULL) {
|
|
game->MT_FreeMaterialTypeArray(materialTypeArray);
|
|
materialTypeArray = NULL;
|
|
materialTypeArrayName = "";
|
|
MTAWidth = 0;
|
|
MTAHeight = 0;
|
|
}
|
|
#endif
|
|
}
|
|
|
|
/*
|
|
==============
|
|
idMaterial::GetEditorImage
|
|
==============
|
|
*/
|
|
idImage* idMaterial::GetEditorImage(void) 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, true, TR_REPEAT, TD_DEFAULT);
|
|
}
|
|
|
|
if (!editorImage) {
|
|
editorImage = globalImages->defaultImage;
|
|
}
|
|
|
|
return editorImage;
|
|
}
|
|
|
|
|
|
// info parms
|
|
typedef struct {
|
|
char* name;
|
|
int clearSolid, surfaceFlags, contents;
|
|
} infoParm_t;
|
|
|
|
#ifdef QUAKE4
|
|
static infoParm_t infoParms[] = {
|
|
// game relevant attributes
|
|
{"solid", 0, 0, CONTENTS_SOLID },
|
|
{"opaque", 0, 0, CONTENTS_OPAQUE },
|
|
{"water", 1, 0, CONTENTS_WATER },
|
|
{"playerclip", 0, 0, CONTENTS_PLAYERCLIP },
|
|
{"monsterclip", 0, 0, CONTENTS_MONSTERCLIP },
|
|
{"moveableclip", 0, 0, CONTENTS_MOVEABLECLIP },
|
|
{"ikclip", 0, 0, CONTENTS_IKCLIP },
|
|
{"blood", 0, 0, CONTENTS_BLOOD },
|
|
{"body", 0, 0, CONTENTS_BODY },
|
|
{"projectile", 0, 0, CONTENTS_PROJECTILE },
|
|
{"corpse", 0, 0, CONTENTS_CORPSE },
|
|
{"rendermodel", 0, 0, CONTENTS_RENDERMODEL },
|
|
{"trigger", 0, 0, CONTENTS_TRIGGER },
|
|
{"aassolid", 0, 0, CONTENTS_AAS_SOLID },
|
|
{"aasobstacle", 0, 0, CONTENTS_AAS_OBSTACLE },
|
|
{"flashlight_trigger", 0, 0, CONTENTS_FLASHLIGHT_TRIGGER },
|
|
{"sightclip", 0, 0, CONTENTS_SIGHTCLIP },
|
|
{"largeshotclip", 0, 0, CONTENTS_LARGESHOTCLIP },
|
|
{"notacticalfeatures", 0, 0, CONTENTS_NOTACTICALFEATURES },
|
|
{"vehicleclip", 0, 0, CONTENTS_VEHICLECLIP },
|
|
{"flyclip", 0, 0, CONTENTS_FLYCLIP },
|
|
{"itemclip", 0, 0, CONTENTS_ITEMCLIP },
|
|
{"projectileclip", 0, 0, CONTENTS_PROJECTILECLIP },
|
|
|
|
{"nonsolid", 1, 0, 0 },
|
|
{"nullNormal", 0, SURF_NULLNORMAL, 0 },
|
|
|
|
// utility relevant attributes
|
|
{"areaportal", 1, 0, CONTENTS_AREAPORTAL },
|
|
{"qer_nocarve", 1, 0, CONTENTS_NOCSG },
|
|
{"nocsg", 1, 0, CONTENTS_NOCSG },
|
|
|
|
// surface behavior attributes
|
|
{"discrete", 1, SURF_DISCRETE, 0 },
|
|
{"noFragment", 0, SURF_NOFRAGMENT, 0 },
|
|
{"slick", 0, SURF_SLICK, 0 },
|
|
{"collision", 0, SURF_COLLISION, 0 },
|
|
{"noimpact", 0, SURF_NOIMPACT, 0 },
|
|
{"nodamage", 0, SURF_NODAMAGE, 0 },
|
|
{"ladder", 0, SURF_LADDER, 0 },
|
|
{"nosteps", 0, SURF_NOSTEPS, 0 },
|
|
{"bounce", 0, SURF_BOUNCE, 0 },
|
|
{"no_t_fix", 0, SURF_NO_T_FIX, 0 },
|
|
{"notfix", 0, SURF_NO_T_FIX, 0 },
|
|
|
|
// material types for particle, sound, footstep feedback
|
|
{"metal", 0, SURFTYPE_METAL, 0 },
|
|
{"stone", 0, SURFTYPE_STONE, 0 },
|
|
{"flesh", 0, SURFTYPE_FLESH, 0 },
|
|
{"wood", 0, SURFTYPE_WOOD, 0 },
|
|
{"cardboard", 0, SURFTYPE_CARDBOARD, 0 },
|
|
{"liquid", 0, SURFTYPE_LIQUID, 0 },
|
|
{"glass", 0, SURFTYPE_GLASS, 0 },
|
|
{"plastic", 0, SURFTYPE_PLASTIC, 0 },
|
|
{"ricochet", 0, SURFTYPE_RICOCHET, 0 },
|
|
{"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 },
|
|
};
|
|
#elif defined( PREY )
|
|
static infoParm_t infoParms[] = {
|
|
// game relevant attributes
|
|
{"solid", 0, 0, CONTENTS_SOLID }, // may need to override a clearSolid
|
|
{"opaque", 0, 0, CONTENTS_OPAQUE }, // blocks visibility / opaque contents
|
|
{"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
|
|
{"body", 0, 0, CONTENTS_BODY }, // body contents
|
|
{"projectile", 0, 0, CONTENTS_PROJECTILE }, // projectile collision
|
|
{"corpse", 0, 0, CONTENTS_CORPSE }, // corpse collision
|
|
{"rendermodel", 0, 0, CONTENTS_RENDERMODEL }, // render model collision contents
|
|
{"trigger", 0, 0, CONTENTS_TRIGGER }, // used for triggers
|
|
{"aassolid", 0, 0, CONTENTS_AAS_SOLID }, // Doom 3-style name
|
|
{"aas_solid", 0, 0, CONTENTS_AAS_SOLID }, // HumanHead cm_contentsNameByIndex name
|
|
{"aasobstacle", 0, 0, CONTENTS_AAS_OBSTACLE }, // Doom 3-style name
|
|
{"aas_obstacle", 0, 0, CONTENTS_AAS_OBSTACLE }, // HumanHead cm_contentsNameByIndex name
|
|
{"flashlight_trigger", 0, 0, CONTENTS_FLASHLIGHT_TRIGGER }, // used for triggers that are activated by the flashlight
|
|
|
|
// HUMANHEAD pdm: added contents
|
|
{"forcefield", 0, 0, CONTENTS_FORCEFIELD }, // forcefield collision/content
|
|
{"spiritbridge", 0, 0, CONTENTS_SPIRITBRIDGE }, // spirit bridge collision/content
|
|
{"areaportal", 1, 0, CONTENTS_AREAPORTAL }, // divides areas
|
|
{"nocsg", 1, 0, CONTENTS_NOCSG }, // don't cut brushes in editor
|
|
{"qer_nocarve", 1, 0, CONTENTS_NOCSG }, // Doom 3 editor alias for nocsg
|
|
{"block_radiusdamage", 0, 0, CONTENTS_BLOCK_RADIUSDAMAGE }, // blocks radius damage
|
|
{"shootable", 0, 0, CONTENTS_SHOOTABLE }, // shootable contents
|
|
{"deathvolume", 0, 0, CONTENTS_DEATHVOLUME }, // death volume contents
|
|
{"vehicleclip", 0, 0, CONTENTS_VEHICLECLIP }, // solid to vehicles
|
|
{"owner_to_owner", 0, 0, CONTENTS_OWNER_TO_OWNER }, // owner-to-owner collision/content filtering
|
|
{"game_portal", 0, 0, CONTENTS_GAME_PORTAL }, // game portal contents
|
|
{"shootablebyarrow", 0, 0, CONTENTS_SHOOTABLEBYARROW }, // shootable by arrow/projectile type
|
|
{"hunterclip", 0, 0, CONTENTS_HUNTERCLIP }, // hunter-specific clipping
|
|
|
|
{"nonsolid", 1, 0, 0 }, // clears the solid flag
|
|
{"nullNormal", 0, SURF_NULLNORMAL, 0 }, // renderbump will draw as 0x80 0x80 0x80
|
|
|
|
// surface behavior attributes
|
|
{"discrete", 1, SURF_DISCRETE, 0 }, // do not merge/clip automatically
|
|
{"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
|
|
{"matter_metal", 0, SURFTYPE_METAL, 0 }, // metal
|
|
{"matter_stone", 0, SURFTYPE_STONE, 0 }, // stone
|
|
{"matter_flesh", 0, SURFTYPE_FLESH, 0 }, // flesh
|
|
{"matter_wood", 0, SURFTYPE_WOOD, 0 }, // wood
|
|
{"matter_cardboard", 0, SURFTYPE_CARDBOARD, 0 }, // cardboard
|
|
{"matter_liquid", 0, SURFTYPE_LIQUID, 0 }, // liquid
|
|
{"matter_glass", 0, SURFTYPE_GLASS, 0 }, // glass
|
|
{"matter_tile", 0, SURFTYPE_TILE, 0 }, // tile
|
|
{"wallwalk", 0, SURFTYPE_WALLWALK, 0 }, // wallwalk surface
|
|
{"matter_altmetal", 0, SURFTYPE_ALTMETAL, 0 }, // alternate metal
|
|
{"forcefield", 0, SURFTYPE_FORCEFIELD, 0 }, // forcefield material
|
|
{"matter_pipe", 0, SURFTYPE_PIPE, 0 }, // pipe
|
|
{"matter_spirit", 0, SURFTYPE_SPIRIT, 0 }, // spirit material
|
|
{"matter_chaff", 0, SURFTYPE_CHAFF, 0 }, // chaff material
|
|
};
|
|
#else
|
|
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 },
|
|
};
|
|
#endif
|
|
|
|
static const int numInfoParms = sizeof(infoParms) / sizeof(infoParms[0]);
|
|
|
|
/*
|
|
===============
|
|
idMaterial::ApplySurfaceParm
|
|
|
|
Alpha E3 materials commonly use the Quake/id Tech 3-style
|
|
"surfaceparm <flag>" wrapper instead of Doom 3's direct material flags.
|
|
===============
|
|
*/
|
|
static bool ApplySurfaceParm( idMaterial *material, idToken *token, materialCoverage_t &coverage ) {
|
|
if ( !token->Icmp( "trans" ) ) {
|
|
coverage = MC_TRANSLUCENT;
|
|
return true;
|
|
}
|
|
|
|
return material->CheckSurfaceParm( token );
|
|
}
|
|
|
|
|
|
/*
|
|
===============
|
|
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;
|
|
#ifdef QUAKE4
|
|
}
|
|
else if (!token.Icmp("preGui") || !token.Icmp("pregui")) {
|
|
sort = SS_PREGUI;
|
|
}
|
|
else if (!token.Icmp("gui")) {
|
|
sort = SS_GUI;
|
|
#endif
|
|
}
|
|
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::ParseDecalInfo
|
|
=================
|
|
*/
|
|
void idMaterial::ParseDecalInfo(idLexer& src) {
|
|
#ifdef QUAKE4
|
|
idToken token;
|
|
|
|
decalInfo.stayTime = src.ParseFloat() * 1000;
|
|
if (src.ReadToken(&token)) {
|
|
if (token == ",") {
|
|
decalInfo.maxAngle = src.ParseFloat();
|
|
}
|
|
else {
|
|
src.UnreadToken(&token);
|
|
}
|
|
}
|
|
#else
|
|
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];
|
|
}
|
|
#endif
|
|
}
|
|
|
|
/*
|
|
=============
|
|
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(void) {
|
|
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(void) {
|
|
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;
|
|
}
|
|
|
|
#ifdef PREY
|
|
if (!token.Icmp("distance")) {
|
|
pd->registersAreConstant = false;
|
|
SetMaterialFlag(MF_USESDISTANCE);
|
|
return EXP_REG_DISTANCE;
|
|
}
|
|
#endif
|
|
|
|
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;
|
|
}
|
|
#ifdef QUAKE4
|
|
if (!token.Icmp("IsMultiplayer")) {
|
|
return GetExpressionConstant(session->IsMultiplayer() ? 1.0f : 0.0f);
|
|
}
|
|
if (!token.Icmp("POTCorrectionX")) {
|
|
const int width = glConfig.vidWidth;
|
|
return GetExpressionConstant(width > 0 ? (float)width / (float)R_MaterialMakePowerOfTwo(width) : 1.0f);
|
|
}
|
|
if (!token.Icmp("POTCorrectionY")) {
|
|
const int height = glConfig.vidHeight;
|
|
return GetExpressionConstant(height > 0 ? (float)height / (float)R_MaterialMakePowerOfTwo(height) : 1.0f);
|
|
}
|
|
if (!token.Icmp("VideoWidth")) {
|
|
return GetExpressionConstant((float)glConfig.vidWidth);
|
|
}
|
|
if (!token.Icmp("VideoHeight")) {
|
|
return GetExpressionConstant((float)glConfig.vidHeight);
|
|
}
|
|
if (!token.Icmp("glslPrograms")) {
|
|
pd->registersAreConstant = false;
|
|
return EmitOp(0, 0, OP_TYPE_GLSL_ENABLED);
|
|
}
|
|
if (!token.Icmp("DecalLife")) {
|
|
pd->registersAreConstant = false;
|
|
return EXP_REG_DECAL_LIFE;
|
|
}
|
|
if (!token.Icmp("DecalSpawn")) {
|
|
pd->registersAreConstant = false;
|
|
return EXP_REG_DECAL_SPAWN;
|
|
}
|
|
if (!token.Icmp("VertexRandomizer")) {
|
|
pd->registersAreConstant = false;
|
|
return EXP_REG_VERTEX_RANDOMIZER;
|
|
}
|
|
#endif
|
|
if (!token.Icmp("fragmentPrograms")) {
|
|
#ifdef PREY
|
|
pd->registersAreConstant = false;
|
|
return EmitOp(0, 0, OP_TYPE_FRAGMENTPROGRAMS);
|
|
#else
|
|
return GetExpressionConstant((float)glConfig.ARBFragmentProgramAvailable);
|
|
#endif
|
|
}
|
|
|
|
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;
|
|
#ifdef QUAKE4
|
|
ss->mStageRegisterStart = numRegisters;
|
|
ss->mNumStageRegisters = 0;
|
|
ss->mStageOpsStart = numOps;
|
|
ss->mNumStageOps = 0;
|
|
ss->hasAlphaFunc = false;
|
|
ss->alphaTestMode = GL_GREATER;
|
|
#endif
|
|
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")) {
|
|
return GLS_SRCBLEND_ALPHA_SATURATE;
|
|
#ifdef QUAKE4
|
|
}
|
|
else if (!name.Icmp("GL_SRC_COLOR")) {
|
|
return GLS_SRCBLEND_SRC_COLOR;
|
|
}
|
|
else if (!name.Icmp("GL_ONE_MINUS_SRC_COLOR")) {
|
|
return GLS_SRCBLEND_ONE_MINUS_SRC_COLOR;
|
|
#endif
|
|
}
|
|
|
|
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;
|
|
#ifdef QUAKE4
|
|
}
|
|
else if (!name.Icmp("GL_DST_COLOR")) {
|
|
return GLS_DSTBLEND_DST_COLOR;
|
|
}
|
|
else if (!name.Icmp("GL_ONE_MINUS_DST_COLOR")) {
|
|
return GLS_DSTBLEND_ONE_MINUS_DST_COLOR;
|
|
#endif
|
|
}
|
|
|
|
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("glowmap")) {
|
|
stage->lighting = SL_GLOWMAP;
|
|
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);
|
|
}
|
|
|
|
|
|
#if defined( QUAKE4 ) || defined( PREY )
|
|
/*
|
|
================
|
|
idMaterial::ParseFragmentParm
|
|
================
|
|
*/
|
|
void idMaterial::ParseFragmentParm(idLexer& src, newShaderStage_t* newStage) {
|
|
idToken token;
|
|
|
|
src.ReadTokenOnLine(&token);
|
|
int parm = token.GetIntValue();
|
|
if (!token.IsNumeric() || parm < 0 || parm >= MAX_FRAGMENT_PARMS) {
|
|
common->Warning("bad fragmentParm number\n");
|
|
SetMaterialFlag(MF_DEFAULTED);
|
|
return;
|
|
}
|
|
if (parm >= newStage->numFragmentParms) {
|
|
newStage->numFragmentParms = parm + 1;
|
|
}
|
|
|
|
newStage->fragmentParms[parm][0] = ParseExpression(src);
|
|
|
|
src.ReadTokenOnLine(&token);
|
|
if (!token[0] || token.Icmp(",")) {
|
|
newStage->fragmentParms[parm][1] =
|
|
newStage->fragmentParms[parm][2] =
|
|
newStage->fragmentParms[parm][3] = newStage->fragmentParms[parm][0];
|
|
return;
|
|
}
|
|
|
|
newStage->fragmentParms[parm][1] = ParseExpression(src);
|
|
|
|
src.ReadTokenOnLine(&token);
|
|
if (!token[0] || token.Icmp(",")) {
|
|
newStage->fragmentParms[parm][2] = GetExpressionConstant(0);
|
|
newStage->fragmentParms[parm][3] = GetExpressionConstant(1);
|
|
return;
|
|
}
|
|
|
|
newStage->fragmentParms[parm][2] = ParseExpression(src);
|
|
|
|
src.ReadTokenOnLine(&token);
|
|
if (!token[0] || token.Icmp(",")) {
|
|
newStage->fragmentParms[parm][3] = GetExpressionConstant(1);
|
|
return;
|
|
}
|
|
|
|
newStage->fragmentParms[parm][3] = ParseExpression(src);
|
|
}
|
|
#endif
|
|
|
|
|
|
/*
|
|
================
|
|
idMaterial::ParseFragmentMap
|
|
================
|
|
*/
|
|
void idMaterial::ParseFragmentMap(idLexer& src, newShaderStage_t* newStage) {
|
|
const char* str;
|
|
textureFilter_t tf;
|
|
textureRepeat_t trp;
|
|
textureDepth_t td;
|
|
cubeFiles_t cubeMap;
|
|
bool allowPicmip;
|
|
idToken token;
|
|
#ifdef QUAKE4
|
|
int imageFlags;
|
|
#endif
|
|
|
|
tf = TF_DEFAULT;
|
|
trp = TR_REPEAT;
|
|
td = TD_DEFAULT;
|
|
allowPicmip = true;
|
|
cubeMap = CF_2D;
|
|
#ifdef QUAKE4
|
|
imageFlags = 0;
|
|
#endif
|
|
|
|
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;
|
|
}
|
|
#ifdef QUAKE4
|
|
if (!token.Icmp("mirroredrepeat")) {
|
|
trp = TR_MIRRORED_REPEAT;
|
|
continue;
|
|
}
|
|
if (!token.Icmp("nomips")) {
|
|
imageFlags |= 1;
|
|
continue;
|
|
}
|
|
#endif
|
|
if (!token.Icmp("forceHighQuality")) {
|
|
td = TD_HIGH_QUALITY;
|
|
continue;
|
|
}
|
|
|
|
if (!token.Icmp("uncompressed") || !token.Icmp("highquality")) {
|
|
if (!globalImages->image_ignoreHighQuality.GetInteger()) {
|
|
td = TD_HIGH_QUALITY;
|
|
}
|
|
continue;
|
|
}
|
|
if (!token.Icmp("nopicmip")) {
|
|
allowPicmip = false;
|
|
continue;
|
|
}
|
|
#ifdef QUAKE4
|
|
if (!token.Icmp("lightfalloffImage")) {
|
|
newStage->fragmentProgramBindings[unit] = 1;
|
|
newStage->fragmentProgramImages[unit] = NULL;
|
|
continue;
|
|
}
|
|
if (!token.Icmp("lightImage")) {
|
|
newStage->fragmentProgramBindings[unit] = 2;
|
|
newStage->fragmentProgramImages[unit] = NULL;
|
|
continue;
|
|
}
|
|
if (!token.Icmp("ambientNormalMap")) {
|
|
newStage->fragmentProgramBindings[unit] = 3;
|
|
newStage->fragmentProgramImages[unit] = globalImages->ambientNormalMap;
|
|
continue;
|
|
}
|
|
if (!token.Icmp("normalCubeMap")) {
|
|
newStage->fragmentProgramBindings[unit] = 4;
|
|
newStage->fragmentProgramImages[unit] = globalImages->normalCubeMapImage;
|
|
continue;
|
|
}
|
|
if (!token.Icmp("specularTableImage")) {
|
|
newStage->fragmentProgramBindings[unit] = 5;
|
|
newStage->fragmentProgramImages[unit] = globalImages->specularTableImage;
|
|
continue;
|
|
}
|
|
#endif
|
|
|
|
// assume anything else is the image name
|
|
src.UnreadToken(&token);
|
|
break;
|
|
}
|
|
str = R_ParsePastImageProgram(src);
|
|
|
|
#ifdef QUAKE4
|
|
if (!newStage->fragmentProgramBindings[unit]) {
|
|
newStage->fragmentProgramImages[unit] =
|
|
globalImages->ImageFromFile(str, tf, allowPicmip, trp, td, cubeMap);
|
|
}
|
|
#else
|
|
newStage->fragmentProgramImages[unit] =
|
|
globalImages->ImageFromFile(str, tf, allowPicmip, trp, td, cubeMap);
|
|
#endif
|
|
#ifdef QUAKE4
|
|
if (!newStage->fragmentProgramImages[unit] && !newStage->fragmentProgramBindings[unit]) {
|
|
newStage->fragmentProgramImages[unit] = globalImages->defaultImage;
|
|
}
|
|
#else
|
|
if (!newStage->fragmentProgramImages[unit]) {
|
|
newStage->fragmentProgramImages[unit] = globalImages->defaultImage;
|
|
}
|
|
#endif
|
|
}
|
|
|
|
/*
|
|
===============
|
|
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;
|
|
textureDepth_t td;
|
|
cubeFiles_t cubeMap;
|
|
bool allowPicmip;
|
|
#ifdef QUAKE4
|
|
int imageFlags;
|
|
bool skipWarning;
|
|
rvNewShaderStage* newShaderStage;
|
|
#endif
|
|
#ifdef PREY
|
|
bool highres;
|
|
bool preyTokenOnlyStage;
|
|
#endif
|
|
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;
|
|
allowPicmip = true;
|
|
#ifdef QUAKE4
|
|
imageFlags = 0;
|
|
skipWarning = false;
|
|
newShaderStage = NULL;
|
|
#endif
|
|
#ifdef PREY
|
|
highres = false;
|
|
preyTokenOnlyStage = false;
|
|
#endif
|
|
cubeMap = CF_2D;
|
|
|
|
imageName[0] = 0;
|
|
|
|
memset(&newStage, 0, sizeof(newStage));
|
|
|
|
ss = &pd->parseStages[numStages];
|
|
ts = &ss->texture;
|
|
|
|
ClearStage(ss);
|
|
#ifdef PREY
|
|
ss->isGlow = false;
|
|
ss->isScopeView = false;
|
|
ss->isShuttleView = false;
|
|
ss->isNotScopeView = false;
|
|
ss->isSpiritWalk = false;
|
|
ss->isNotSpiritWalk = false;
|
|
ss->specular.exponent = 22.0f;
|
|
ss->specular.brightness = 1.4f;
|
|
#endif
|
|
|
|
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;
|
|
}
|
|
#ifdef QUAKE4
|
|
if (!token.Icmp("reflectionRenderMap")) {
|
|
ts->dynamic = DI_REFLECTION_RENDER;
|
|
ts->width = src.ParseInt();
|
|
ts->height = src.ParseInt();
|
|
continue;
|
|
}
|
|
if (!token.Icmp("refractionRenderMap")) {
|
|
ts->dynamic = DI_REFRACTION_RENDER;
|
|
ts->width = src.ParseInt();
|
|
ts->height = src.ParseInt();
|
|
ts->texgen = TG_SCREEN;
|
|
continue;
|
|
}
|
|
if (!token.Icmp("cubeRenderMap")) {
|
|
ts->dynamic = DI_CUBE_RENDER;
|
|
ts->width = src.ParseInt();
|
|
ts->height = ts->width;
|
|
ts->texgen = TG_REFLECT_CUBE;
|
|
continue;
|
|
}
|
|
#endif
|
|
#ifdef PREY
|
|
if (!token.Icmp("portalRenderMap")) {
|
|
ts->dynamic = DI_PORTAL_RENDER;
|
|
ts->width = src.ParseInt();
|
|
ts->height = src.ParseInt();
|
|
ts->texgen = TG_SCREEN;
|
|
continue;
|
|
}
|
|
if (!token.Icmp("skyboxRenderMap")) {
|
|
ts->dynamic = DI_SKYBOX_RENDER;
|
|
ts->width = src.ParseInt();
|
|
ts->height = src.ParseInt();
|
|
ts->texgen = TG_SCREEN;
|
|
continue;
|
|
}
|
|
#endif
|
|
|
|
#ifndef QUAKE4
|
|
if (!token.Icmp("xrayRenderMap")) {
|
|
ts->dynamic = DI_XRAY_RENDER;
|
|
ts->width = src.ParseInt();
|
|
ts->height = src.ParseInt();
|
|
ts->texgen = TG_SCREEN;
|
|
continue;
|
|
}
|
|
#endif
|
|
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;
|
|
}
|
|
#ifdef QUAKE4
|
|
if (!token.Icmp("nomips")) {
|
|
imageFlags |= 1;
|
|
continue;
|
|
}
|
|
#endif
|
|
|
|
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 idSndWindow();
|
|
ts->cinematic->InitFromFile(token.c_str(), true);
|
|
continue;
|
|
}
|
|
#ifdef PREY
|
|
if (!token.Icmp("profilemap")) {
|
|
if (!src.ReadToken(&token)) {
|
|
common->Warning("missing parameter for 'profilemap' keyword in material '%s'", GetName());
|
|
continue;
|
|
}
|
|
// Prey creates an hhProfilerGraph cinematic here. Consume the token so
|
|
// retail Prey materials parse even in builds without that runtime class.
|
|
preyTokenOnlyStage = true;
|
|
continue;
|
|
}
|
|
#endif
|
|
|
|
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;
|
|
}
|
|
#ifdef QUAKE4
|
|
if (!token.Icmp("mirroredrepeat")) {
|
|
trp = TR_MIRRORED_REPEAT;
|
|
continue;
|
|
}
|
|
#endif
|
|
if (!token.Icmp("uncompressed") || !token.Icmp("highquality")) {
|
|
if (!globalImages->image_ignoreHighQuality.GetInteger()) {
|
|
td = TD_HIGH_QUALITY;
|
|
}
|
|
continue;
|
|
}
|
|
if (!token.Icmp("forceHighQuality")) {
|
|
td = TD_HIGH_QUALITY;
|
|
continue;
|
|
}
|
|
if (!token.Icmp("nopicmip")) {
|
|
allowPicmip = false;
|
|
continue;
|
|
}
|
|
#ifdef PREY
|
|
if (!token.Icmp("highres")) {
|
|
// The Hex-Rays build records this and passes it to Prey's image manager.
|
|
// This Doom 3-side patch only needs to accept the token safely.
|
|
highres = true;
|
|
continue;
|
|
}
|
|
#endif
|
|
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;
|
|
}
|
|
|
|
#ifdef PREY
|
|
if (!token.Icmp("glowStage")) {
|
|
ss->isGlow = true;
|
|
continue;
|
|
}
|
|
if (!token.Icmp("scopeView")) {
|
|
ss->isScopeView = true;
|
|
continue;
|
|
}
|
|
if (!token.Icmp("shuttleView")) {
|
|
ss->isShuttleView = true;
|
|
continue;
|
|
}
|
|
if (!token.Icmp("notScopeView")) {
|
|
ss->isNotScopeView = true;
|
|
continue;
|
|
}
|
|
if (!token.Icmp("spiritWalk")) {
|
|
ss->isSpiritWalk = true;
|
|
continue;
|
|
}
|
|
if (!token.Icmp("notSpiritWalk")) {
|
|
ss->isNotSpiritWalk = true;
|
|
continue;
|
|
}
|
|
#endif
|
|
|
|
// 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);
|
|
#ifdef QUAKE4
|
|
}
|
|
else if (!token.Icmp("potCorrection")) {
|
|
if (!glConfig.textureNonPowerOfTwoAvailable) {
|
|
ts->texgen = TG_POT_CORRECTION;
|
|
}
|
|
#endif
|
|
#ifdef PREY
|
|
}
|
|
else if (!token.Icmp("screen")) {
|
|
ts->texgen = TG_SCREEN;
|
|
#endif
|
|
}
|
|
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;
|
|
#ifdef QUAKE4
|
|
if (!ss->hasAlphaFunc) {
|
|
ss->alphaTestMode = GL_GREATER;
|
|
}
|
|
#endif
|
|
continue;
|
|
}
|
|
#ifdef QUAKE4
|
|
if (!token.Icmp("alphaFunc")) {
|
|
ss->hasAlphaFunc = true;
|
|
ss->hasAlphaTest = true;
|
|
ss->alphaTestMode = GL_GREATER;
|
|
if (src.ReadToken(&token)) {
|
|
if (!token.Icmp("less")) {
|
|
ss->alphaTestMode = GL_LESS;
|
|
}
|
|
else if (!token.Icmp("equal")) {
|
|
ss->alphaTestMode = GL_EQUAL;
|
|
}
|
|
else if (!token.Icmp("greater")) {
|
|
ss->alphaTestMode = GL_GREATER;
|
|
}
|
|
else {
|
|
common->Warning("unknown alpha func '%s' in material '%s'", token.c_str(), GetName());
|
|
}
|
|
}
|
|
continue;
|
|
}
|
|
#endif
|
|
|
|
// 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;
|
|
}
|
|
#ifdef PREY
|
|
if (!token.Icmp("specularExp")) {
|
|
ss->specular.exponent = src.ParseFloat();
|
|
MatchToken(src, ",");
|
|
ss->specular.brightness = src.ParseFloat();
|
|
continue;
|
|
}
|
|
#endif
|
|
|
|
if (!token.Icmp("if")) {
|
|
ss->conditionRegister = ParseExpression(src);
|
|
continue;
|
|
}
|
|
|
|
|
|
#if 0 // QUAKE4 <-- disabled shader stuff
|
|
if (!token.Icmp("program")) {
|
|
if (src.ReadTokenOnLine(&token)) {
|
|
newStage.vertexProgram = R_FindARBProgram(GL_VERTEX_PROGRAM_ARB, token.c_str());
|
|
newStage.fragmentProgram = R_FindARBProgram(GL_FRAGMENT_PROGRAM_ARB, token.c_str());
|
|
if (!newStage.fragmentProgram) {
|
|
newStage.vertexProgram = 0;
|
|
skipWarning = true;
|
|
}
|
|
#if defined( _MD5R_SUPPORT ) || defined( Q4SDK_MD5R )
|
|
idStr md5rProgram = "md5r";
|
|
md5rProgram += token.c_str();
|
|
newStage.md5rVertexProgram = R_FindARBProgram(GL_VERTEX_PROGRAM_ARB, md5rProgram.c_str());
|
|
#endif
|
|
}
|
|
else {
|
|
common->Warning("missing parameter for 'program' keyword in material '%s'", GetName());
|
|
}
|
|
continue;
|
|
}
|
|
if (!token.Icmp("glslProgram")) {
|
|
if (newShaderStage) {
|
|
common->Warning("ParseStage - glslProgram: Shader program already set!");
|
|
}
|
|
else {
|
|
newShaderStage = new rvGLSLShaderStage;
|
|
}
|
|
if (newShaderStage && !newShaderStage->ParseProgram(src, this)) {
|
|
skipWarning = true;
|
|
}
|
|
continue;
|
|
}
|
|
if (!token.Icmp("shaderParm")) {
|
|
if (newShaderStage) {
|
|
newShaderStage->ParseShaderParm(src, this);
|
|
}
|
|
else {
|
|
common->Warning("ParseStage: shaderParm set before shader type declared.");
|
|
}
|
|
continue;
|
|
}
|
|
if (!token.Icmp("shaderTexture")) {
|
|
if (newShaderStage) {
|
|
newShaderStage->ParseTextureParm(src, this, trpDefault);
|
|
}
|
|
else {
|
|
common->Warning("ParseStage: shaderTexture set before shader type declared.");
|
|
}
|
|
continue;
|
|
}
|
|
if (!token.Icmp("fragmentProgram")) {
|
|
if (src.ReadTokenOnLine(&token)) {
|
|
newStage.fragmentProgram = R_FindARBProgram(GL_FRAGMENT_PROGRAM_ARB, token.c_str());
|
|
}
|
|
else {
|
|
common->Warning("missing parameter for 'fragmentProgram' keyword in material '%s'", GetName());
|
|
}
|
|
continue;
|
|
}
|
|
if (!token.Icmp("vertexProgram")) {
|
|
if (src.ReadTokenOnLine(&token)) {
|
|
newStage.vertexProgram = R_FindARBProgram(GL_VERTEX_PROGRAM_ARB, token.c_str());
|
|
#if defined( _MD5R_SUPPORT ) || defined( Q4SDK_MD5R )
|
|
idStr md5rProgram = "md5r";
|
|
md5rProgram += token.c_str();
|
|
newStage.md5rVertexProgram = R_FindARBProgram(GL_VERTEX_PROGRAM_ARB, md5rProgram.c_str());
|
|
#endif
|
|
}
|
|
else {
|
|
common->Warning("missing parameter for 'vertexProgram' keyword in material '%s'", GetName());
|
|
}
|
|
continue;
|
|
}
|
|
if (!token.Icmp("megaTexture")) {
|
|
if (src.ReadTokenOnLine(&token)) {
|
|
newStage.megaTexture = new idMegaTexture;
|
|
if (newStage.megaTexture->InitFromMegaFile(token.c_str())) {
|
|
newStage.vertexProgram = R_FindARBProgram(GL_VERTEX_PROGRAM_ARB, "megaTexture.vfp");
|
|
newStage.fragmentProgram = R_FindARBProgram(GL_FRAGMENT_PROGRAM_ARB, "megaTexture.vfp");
|
|
}
|
|
else {
|
|
delete newStage.megaTexture;
|
|
newStage.megaTexture = NULL;
|
|
SetMaterialFlag(MF_DEFAULTED);
|
|
}
|
|
}
|
|
else {
|
|
common->Warning("missing parameter for 'megaTexture' keyword in material '%s'", GetName());
|
|
}
|
|
continue;
|
|
}
|
|
if (!token.Icmp("vertexParm")) {
|
|
ParseVertexParm(src, &newStage);
|
|
continue;
|
|
}
|
|
if (!token.Icmp("fragmentParm")) {
|
|
ParseFragmentParm(src, &newStage);
|
|
continue;
|
|
}
|
|
if (!token.Icmp("fragmentMap")) {
|
|
ParseFragmentMap(src, &newStage);
|
|
continue;
|
|
}
|
|
#endif
|
|
#ifdef PREY
|
|
if (!token.Icmp("program")) {
|
|
if (!src.ReadTokenOnLine(&token)) {
|
|
common->Warning("missing parameter for 'program' keyword in material '%s'", GetName());
|
|
}
|
|
preyTokenOnlyStage = true;
|
|
continue;
|
|
}
|
|
if (!token.Icmp("fragmentProgram")) {
|
|
if (!src.ReadTokenOnLine(&token)) {
|
|
common->Warning("missing parameter for 'fragmentProgram' keyword in material '%s'", GetName());
|
|
}
|
|
preyTokenOnlyStage = true;
|
|
continue;
|
|
}
|
|
if (!token.Icmp("vertexProgram")) {
|
|
if (!src.ReadTokenOnLine(&token)) {
|
|
common->Warning("missing parameter for 'vertexProgram' keyword in material '%s'", GetName());
|
|
}
|
|
preyTokenOnlyStage = true;
|
|
continue;
|
|
}
|
|
if (!token.Icmp("megaTexture")) {
|
|
// Full Prey allocates idMegaTexture and binds megaTexture.vfp.
|
|
// Keep this parser-only path dependency-free.
|
|
if (!src.ReadTokenOnLine(&token)) {
|
|
common->Warning("missing parameter for 'megaTexture' keyword in material '%s'", GetName());
|
|
}
|
|
preyTokenOnlyStage = true;
|
|
continue;
|
|
}
|
|
if (!token.Icmp("vertexParm") || !token.Icmp("fragmentParm") || !token.Icmp("fragmentMap")) {
|
|
// These are all single-line shader-program directives in the dump.
|
|
src.SkipRestOfLine();
|
|
preyTokenOnlyStage = true;
|
|
continue;
|
|
}
|
|
|
|
if (!token.Icmp("shaderLevel1") || !token.Icmp("shaderLevel2") || !token.Icmp("shaderLevel3") ||
|
|
!token.Icmp("shaderFallback1") || !token.Icmp("shaderFallback2") || !token.Icmp("shaderFallback3")) {
|
|
preyTokenOnlyStage = true;
|
|
continue;
|
|
}
|
|
#endif
|
|
|
|
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) {
|
|
ss->newStage = (newShaderStage_t*)Mem_Alloc(sizeof(newStage));
|
|
*(ss->newStage) = newStage;
|
|
}
|
|
#if 0
|
|
if (newShaderStage) {
|
|
ss->newShaderStage = newShaderStage;
|
|
newShaderStage->Resolve();
|
|
}
|
|
#endif
|
|
|
|
// 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;
|
|
}
|
|
}
|
|
|
|
// now load the image with all the parms we parsed
|
|
#ifdef PREY
|
|
(void)highres; // parsed for Prey material compatibility; Doom 3 ImageFromFile has no highres arg.
|
|
#endif
|
|
if (imageName[0]) {
|
|
#ifdef QUAKE4
|
|
ts->image = globalImages->ImageFromFile(imageName, tf, allowPicmip, trp, td, cubeMap);
|
|
#else
|
|
ts->image = globalImages->ImageFromFile(imageName, tf, allowPicmip, trp, td, cubeMap);
|
|
#endif
|
|
if (!ts->image) {
|
|
ts->image = globalImages->defaultImage;
|
|
}
|
|
|
|
if (r_trainmats.GetBool())
|
|
{
|
|
ts->image->ActuallyLoadImage(false, false);
|
|
}
|
|
}
|
|
else if (!ts->cinematic && !ts->dynamic && !ss->newStage
|
|
#ifdef QUAKE4
|
|
&& !ss->newShaderStage && !skipWarning
|
|
#endif
|
|
#ifdef PREY
|
|
&& !preyTokenOnlyStage
|
|
#endif
|
|
) {
|
|
common->Warning("material '%s' had stage with no image", GetName());
|
|
ts->image = globalImages->defaultImage;
|
|
}
|
|
#ifdef QUAKE4
|
|
ss->mNumStageRegisters = numRegisters - ss->mStageRegisterStart;
|
|
ss->mNumStageOps = numOps - ss->mStageOpsStart;
|
|
#endif
|
|
}
|
|
|
|
/*
|
|
===============
|
|
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;
|
|
}
|
|
#ifdef QUAKE4
|
|
if (!token.Icmp("rectsprite")) {
|
|
deform = DFRM_RECTSPRITE;
|
|
cullType = CT_TWO_SIDED;
|
|
SetMaterialFlag(MF_NOSHADOWS);
|
|
return;
|
|
}
|
|
#endif
|
|
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;
|
|
}
|
|
#ifndef QUAKE4
|
|
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;
|
|
}
|
|
#endif
|
|
src.Warning("Bad deform type '%s'", token.c_str());
|
|
SetMaterialFlag(MF_DEFAULTED);
|
|
}
|
|
|
|
/*
|
|
===================
|
|
idMaterial::GetDiffuseImage
|
|
===================
|
|
*/
|
|
idImage* idMaterial::GetDiffuseImage(const float* regs) const {
|
|
for (int i = 0; i < numStages; i++) {
|
|
if (regs && regs[stages[i].conditionRegister] == 0) {
|
|
continue;
|
|
}
|
|
|
|
if (stages[i].lighting == SL_DIFFUSE && stages[i].texture.image) {
|
|
return stages[i].texture.image;
|
|
}
|
|
}
|
|
return GetEditorImage();
|
|
}
|
|
|
|
/*
|
|
===================
|
|
idMaterial::IsLitMaterial
|
|
===================
|
|
*/
|
|
bool idMaterial::IsLitMaterial(void) const {
|
|
for (int i = 0; i < numStages; i++) {
|
|
if (stages[i].lighting == SL_DIFFUSE && stages[i].texture.image) {
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
/*
|
|
===================
|
|
idMaterial::IsSky
|
|
===================
|
|
*/
|
|
bool idMaterial::IsSky(void) const {
|
|
for (int i = 0; i < numStages; i++) {
|
|
if (stages[i].texture.texgen == TG_SKYBOX_CUBE) {
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
|
|
#ifdef QUAKE4
|
|
/*
|
|
===================
|
|
idMaterial::GetTexelCount
|
|
===================
|
|
*/
|
|
int idMaterial::GetTexelCount(void) const {
|
|
int count = 0;
|
|
if (!stages) {
|
|
return 0;
|
|
}
|
|
for (int i = 0; i < numStages; i++) {
|
|
if (stages[i].texture.image) {
|
|
count += stages[i].texture.image->uploadWidth * stages[i].texture.image->uploadHeight;
|
|
}
|
|
}
|
|
return count;
|
|
}
|
|
|
|
/*
|
|
===================
|
|
idMaterial::HasDefaultedImage
|
|
===================
|
|
*/
|
|
bool idMaterial::HasDefaultedImage(void) const {
|
|
for (int i = 0; i < numStages; i++) {
|
|
if (stages[i].texture.image && stages[i].texture.image->defaulted) {
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
/*
|
|
===================
|
|
idMaterial::GetDiffuseImage
|
|
===================
|
|
*/
|
|
idImage* idMaterial::GetDiffuseImage(void) const {
|
|
if (numStages > 0 && stages && stages[0].texture.image) {
|
|
return stages[0].texture.image;
|
|
}
|
|
return GetEditorImage();
|
|
}
|
|
|
|
/*
|
|
===================
|
|
idMaterial::GetMaterialType
|
|
===================
|
|
*/
|
|
const rvDeclMatType* idMaterial::GetMaterialType(idVec2& tc) const {
|
|
if (!materialTypeArray || MTAWidth <= 0 || MTAHeight <= 0) {
|
|
return materialType;
|
|
}
|
|
|
|
tc.x = fmod(tc.x, 1.0f);
|
|
tc.y = fmod(tc.y, 1.0f);
|
|
if (tc.x < 0.0f) {
|
|
tc.x += 1.0f;
|
|
}
|
|
if (tc.y < 0.0f) {
|
|
tc.y += 1.0f;
|
|
}
|
|
|
|
int x = (int)(tc.x * MTAWidth);
|
|
int y = (int)(tc.y * MTAHeight);
|
|
if (x < 0) x = 0;
|
|
if (y < 0) y = 0;
|
|
if (x >= MTAWidth) x = MTAWidth - 1;
|
|
if (y >= MTAHeight) y = MTAHeight - 1;
|
|
|
|
const int index = materialTypeArray[y * MTAWidth + x];
|
|
const rvDeclMatType* type = static_cast<const rvDeclMatType*>(declManager->DeclByIndex(static_cast<declType_t>(6), index));
|
|
return type ? type : materialType;
|
|
}
|
|
|
|
/*
|
|
===================
|
|
idMaterial::ResolveUse
|
|
===================
|
|
*/
|
|
void idMaterial::ResolveUse(void) {
|
|
for (int i = 0; i < numStages; i++) {
|
|
shaderStage_t* stage = &stages[i];
|
|
if (stage->newStage) {
|
|
for (int j = 0; j < stage->newStage->numFragmentProgramImages; j++) {
|
|
if (stage->newStage->fragmentProgramImages[j]) {
|
|
stage->newStage->fragmentProgramImages[j]->useCount += useCount;
|
|
}
|
|
}
|
|
}
|
|
#if 0
|
|
else if (stage->newShaderStage) {
|
|
for (int j = 0; j < MAX_FRAGMENT_IMAGES; j++) {
|
|
idImage* image = stage->newShaderStage->textureParmImages[j];
|
|
if (image) {
|
|
image->useCount += useCount;
|
|
}
|
|
}
|
|
}
|
|
#endif
|
|
else if (stage->texture.image) {
|
|
stage->texture.image->useCount += useCount;
|
|
}
|
|
}
|
|
if (lightFalloffImage) {
|
|
lightFalloffImage->useCount += useCount;
|
|
}
|
|
if (portalImage) {
|
|
portalImage->useCount += useCount;
|
|
}
|
|
}
|
|
|
|
/*
|
|
===================
|
|
idMaterial::Validate
|
|
===================
|
|
*/
|
|
bool idMaterial::Validate(const char* psText, int iTextLength, idStr& strReportTo) const {
|
|
strReportTo.Clear();
|
|
return true;
|
|
}
|
|
#endif
|
|
|
|
/*
|
|
===================
|
|
idMaterial::GetBumpImage
|
|
===================
|
|
*/
|
|
idImage* idMaterial::GetBumpImage(void) const {
|
|
for (int i = 0; i < numStages; i++) {
|
|
if (stages[i].lighting == SL_BUMP && stages[i].texture.image) {
|
|
return stages[i].texture.image;
|
|
}
|
|
}
|
|
return GetEditorImage();
|
|
}
|
|
|
|
/*
|
|
===================
|
|
idMaterial::GetSpecImage
|
|
===================
|
|
*/
|
|
idImage* idMaterial::GetSpecImage(void) const {
|
|
for (int i = 0; i < numStages; i++) {
|
|
if (stages[i].lighting == SL_SPECULAR && stages[i].texture.image) {
|
|
return stages[i].texture.image;
|
|
}
|
|
}
|
|
return globalImages->blackImage;
|
|
}
|
|
|
|
/*
|
|
===================
|
|
idMaterial::GetGlowImage
|
|
===================
|
|
*/
|
|
idImage* idMaterial::GetGlowImage(const float* regs) const {
|
|
#ifdef PREY
|
|
for (int i = 0; i < numStages; i++) {
|
|
if (regs && regs[stages[i].conditionRegister] == 0) {
|
|
continue;
|
|
}
|
|
|
|
if ((stages[i].lighting == SL_GLOWMAP || stages[i].isGlow) && stages[i].texture.image) {
|
|
return stages[i].texture.image;
|
|
}
|
|
}
|
|
#endif
|
|
return NULL;
|
|
}
|
|
|
|
/*
|
|
==============
|
|
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;
|
|
|
|
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;
|
|
}
|
|
else if ( !token.Icmp( "qer_trans" ) ) {
|
|
src.SkipRestOfLine();
|
|
continue;
|
|
}
|
|
// description
|
|
else if (!token.Icmp("description")) {
|
|
src.ReadTokenOnLine(&token);
|
|
desc = token.c_str();
|
|
continue;
|
|
}
|
|
else if ( !token.Icmp( "surfaceparm" ) ) {
|
|
if ( src.ReadTokenOnLine( &token ) && ApplySurfaceParm( this, &token, coverage ) ) {
|
|
src.SkipRestOfLine();
|
|
continue;
|
|
}
|
|
src.Warning( "unknown surfaceparm '%s' in '%s'", token.c_str(), GetName() );
|
|
src.SkipRestOfLine();
|
|
continue;
|
|
}
|
|
// check for the surface / content bit flags
|
|
else if (CheckSurfaceParm(&token)) {
|
|
continue;
|
|
}
|
|
else if ( !token.Icmp( "nodraw" ) ) {
|
|
SetMaterialFlag( MF_NOSHADOWS );
|
|
continue;
|
|
}
|
|
else if ( !token.Icmp( "trans" ) ) {
|
|
coverage = MC_TRANSLUCENT;
|
|
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;
|
|
}
|
|
#ifdef QUAKE4
|
|
else if (!token.Icmp("needCurrentRender")) {
|
|
SetMaterialFlag(MF_NEED_CURRENT_RENDER);
|
|
continue;
|
|
}
|
|
else if (!token.Icmp("materialType")) {
|
|
src.ReadTokenOnLine(&token);
|
|
materialType = static_cast<const rvDeclMatType*>(declManager->FindType(static_cast<declType_t>(6), token.c_str(), false));
|
|
if (materialType && materialType->IsImplicit()) {
|
|
common->Warning("UNKNOWN: materialType '%s' in '%s'", token.c_str(), GetName());
|
|
}
|
|
continue;
|
|
}
|
|
else if (!token.Icmp("materialImage")) {
|
|
src.ReadTokenOnLine(&token);
|
|
idStr materialImageName = token.c_str();
|
|
materialTypeArray = game->MT_GetMaterialTypeArray(materialImageName, MTAWidth, MTAHeight);
|
|
materialTypeArrayName = token.c_str();
|
|
continue;
|
|
}
|
|
else if (!token.Icmp("sky")) {
|
|
SetMaterialFlag(MF_SKY);
|
|
continue;
|
|
}
|
|
else if (!token.Icmp("portalDistanceNear")) {
|
|
portalDistanceNear = src.ParseFloat();
|
|
continue;
|
|
}
|
|
else if (!token.Icmp("portalDistanceFar")) {
|
|
portalDistanceFar = src.ParseFloat();
|
|
continue;
|
|
}
|
|
else if (!token.Icmp("portalImage")) {
|
|
src.ReadTokenOnLine(&token);
|
|
portalImage = globalImages->ImageFromFile(token.c_str(), TF_DEFAULT, true, TR_CLAMP, TD_DEFAULT);
|
|
src.SkipRestOfLine();
|
|
continue;
|
|
}
|
|
#endif
|
|
#ifdef PREY
|
|
// HUMANHEAD PCF: explicitly skip collision clip for alpha-coverage surfaces.
|
|
else if (!token.Icmp("skipClip")) {
|
|
SetMaterialFlag(MF_SKIPCLIP);
|
|
continue;
|
|
}
|
|
// HUMANHEAD bjk: don't cull tris with the light bounds.
|
|
else if (!token.Icmp("lightWholeMesh")) {
|
|
SetMaterialFlag(MF_LIGHT_WHOLE_MESH);
|
|
continue;
|
|
}
|
|
#endif
|
|
// 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, false, 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;
|
|
}
|
|
// 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;
|
|
}
|
|
#ifdef QUAKE4
|
|
// Quake 4 material guide directives are metadata/preprocessor hints.
|
|
// Treat them as no-op at runtime so shipped materials don't default.
|
|
else if (!token.Icmp("inlineGuide") || !token.Icmp("guide")) {
|
|
idToken guideName;
|
|
if (src.ReadToken(&guideName)) {
|
|
if (src.CheckTokenString("(")) {
|
|
int parenDepth = 1;
|
|
idToken guideToken;
|
|
while (parenDepth > 0 && src.ReadToken(&guideToken)) {
|
|
if (guideToken == "(") {
|
|
parenDepth++;
|
|
}
|
|
else if (guideToken == ")") {
|
|
parenDepth--;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
continue;
|
|
}
|
|
#endif
|
|
else if (!token.Icmp("glowmap")) {
|
|
str = R_ParsePastImageProgram(src);
|
|
idStr::snPrintf(buffer, sizeof(buffer), "blend glowmap\nmap %s\n}\n", str);
|
|
newSrc.LoadMemory(buffer, strlen(buffer), "glowmap");
|
|
newSrc.SetFlags(LEXFL_NOFATALERRORS | LEXFL_NOSTRINGCONCAT | LEXFL_NOSTRINGESCAPECHARS | LEXFL_ALLOWPATHNAMES);
|
|
ParseStage(newSrc, trpDefault);
|
|
newSrc.FreeSource();
|
|
continue;
|
|
}
|
|
|
|
#ifdef PREY
|
|
else if (!token.Icmp("highres")) {
|
|
continue;
|
|
}
|
|
#endif
|
|
|
|
// 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;
|
|
#ifdef PREY
|
|
surfaceFlags |= SURF_NOIMPACT;
|
|
#endif
|
|
contentFlags &= ~CONTENTS_SOLID;
|
|
|
|
// sort decal
|
|
sort = SS_DECAL;
|
|
|
|
// noShadows
|
|
SetMaterialFlag(MF_NOSHADOWS);
|
|
#ifdef PREY
|
|
coverage = MC_TRANSLUCENT;
|
|
allowOverlays = false;
|
|
#endif
|
|
continue;
|
|
}
|
|
#ifdef PREY
|
|
// HUMANHEAD tmj: render this subview as a skybox portal.
|
|
else if (!token.Icmp("skyboxPortal")) {
|
|
sort = SS_SUBVIEW;
|
|
coverage = MC_OPAQUE;
|
|
subviewClass = SC_PORTAL_SKYBOX;
|
|
continue;
|
|
}
|
|
// HUMANHEAD CJR: render this subview as a direct portal, with distance-cull expression.
|
|
else if (!token.Icmp("directPortal")) {
|
|
sort = SS_SUBVIEW;
|
|
coverage = MC_OPAQUE;
|
|
subviewClass = SC_PORTAL;
|
|
directPortalDistance = ParseExpressionPriority(src, 4);
|
|
continue;
|
|
}
|
|
else if (!token.Icmp("DECAL_ALPHATEST_MACRO")) {
|
|
surfaceFlags |= SURF_NOIMPACT | SURF_DISCRETE;
|
|
contentFlags &= ~CONTENTS_SOLID;
|
|
sort = SS_DECAL;
|
|
SetMaterialFlag(MF_NOSHADOWS);
|
|
coverage = MC_TRANSLUCENT;
|
|
allowOverlays = false;
|
|
continue;
|
|
}
|
|
else if (!token.Icmp("SCORCH_MACRO") || !token.Icmp("OVERLAY_MACRO")) {
|
|
SetMaterialFlag(MF_POLYGONOFFSET);
|
|
polygonOffset = 1;
|
|
surfaceFlags |= SURF_NOIMPACT | SURF_DISCRETE;
|
|
contentFlags &= ~CONTENTS_SOLID;
|
|
sort = SS_DECAL;
|
|
SetMaterialFlag(MF_NOSHADOWS);
|
|
coverage = MC_TRANSLUCENT;
|
|
allowOverlays = false;
|
|
continue;
|
|
}
|
|
else if (!token.Icmp("GLASS_MACRO")) {
|
|
coverage = MC_TRANSLUCENT;
|
|
SetMaterialFlag(MF_NOSHADOWS);
|
|
pd->forceOverlays = true;
|
|
contentFlags &= ~CONTENTS_OPAQUE;
|
|
surfaceFlags = (surfaceFlags & ~SURF_TYPE_MASK) | SURFTYPE_GLASS;
|
|
sort = SS_MEDIUM;
|
|
continue;
|
|
}
|
|
else if (!token.Icmp("ALPHA_MACRO")) {
|
|
coverage = MC_TRANSLUCENT;
|
|
SetMaterialFlag(MF_NOSHADOWS);
|
|
surfaceFlags |= SURF_NOIMPACT;
|
|
cullType = CT_TWO_SIDED;
|
|
continue;
|
|
}
|
|
else if (!token.Icmp("EFFECT_MACRO")) {
|
|
coverage = MC_TRANSLUCENT;
|
|
SetMaterialFlag(MF_NOSHADOWS);
|
|
contentFlags &= ~CONTENTS_SOLID;
|
|
surfaceFlags |= SURF_NOIMPACT;
|
|
cullType = CT_TWO_SIDED;
|
|
continue;
|
|
}
|
|
else if (!token.Icmp("SPRITE_MACRO")) {
|
|
surfaceFlags |= SURF_DISCRETE;
|
|
contentFlags &= ~CONTENTS_SOLID;
|
|
SetMaterialFlag(MF_NOSHADOWS);
|
|
coverage = MC_TRANSLUCENT;
|
|
deform = DFRM_SPRITE;
|
|
cullType = CT_TWO_SIDED;
|
|
continue;
|
|
}
|
|
else if (!token.Icmp("SKYBOX_MACRO")) {
|
|
SetMaterialFlag(MF_NOSHADOWS | MF_NOSELFSHADOW);
|
|
allowOverlays = false;
|
|
coverage = MC_OPAQUE;
|
|
surfaceFlags |= SURF_NOIMPACT | SURF_NOFRAGMENT;
|
|
continue;
|
|
}
|
|
#endif
|
|
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) {
|
|
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
|
|
|
|
if (r_trainmats.GetBool())
|
|
{
|
|
globalImages->keepPixelsResident = true;
|
|
}
|
|
|
|
// 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
|
|
|
|
#ifdef QUAKE4
|
|
if (!TestMaterialFlag(MF_NEED_CURRENT_RENDER)) {
|
|
#endif
|
|
for (i = 0; i < numStages; i++) {
|
|
shaderStage_t* pStage = &pd->parseStages[i];
|
|
if (pStage->texture.image == globalImages->currentRenderImage) {
|
|
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->currentRenderImage) {
|
|
if (sort != SS_PORTAL_SKY) {
|
|
sort = SS_POST_PROCESS;
|
|
coverage = MC_TRANSLUCENT;
|
|
}
|
|
i = numStages;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
#ifdef QUAKE4
|
|
}
|
|
#endif
|
|
|
|
#ifdef QUAKE4
|
|
if ((portalDistanceNear < 262144.0f || portalDistanceFar < 262144.0f) && !portalImage) {
|
|
portalImage = globalImages->blackImage;
|
|
}
|
|
#endif
|
|
|
|
// 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]));
|
|
memcpy(stages, pd->parseStages, numStages * sizeof(stages[0]));
|
|
}
|
|
|
|
if (numOps) {
|
|
ops = (expOp_t*)R_StaticAlloc(numOps * sizeof(ops[0]));
|
|
memcpy(ops, pd->shaderOps, numOps * sizeof(ops[0]));
|
|
}
|
|
|
|
if (numRegisters) {
|
|
expressionRegisters = (float*)R_StaticAlloc(numRegisters * sizeof(expressionRegisters[0]));
|
|
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();
|
|
|
|
pd = NULL; // the pointer will be invalid after exiting this function
|
|
|
|
// finish things up
|
|
if (TestMaterialFlag(MF_DEFAULTED)) {
|
|
MakeDefault();
|
|
return false;
|
|
}
|
|
|
|
if (IsLitMaterial() && r_trainmats.GetBool())
|
|
{
|
|
ExportNatMaterial(this, GetName());
|
|
globalImages->keepPixelsResident = false;
|
|
}
|
|
|
|
LoadNeuralBytesForMaterial(GetName());
|
|
|
|
return true;
|
|
}
|
|
|
|
/*
|
|
===================
|
|
idMaterial::Print
|
|
===================
|
|
*/
|
|
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",
|
|
"OP_TYPE_SOUND"
|
|
#ifdef PREY
|
|
, "OP_TYPE_FRAGMENTPROGRAMS"
|
|
#endif
|
|
#ifdef QUAKE4
|
|
, "OP_TYPE_GLSL_ENABLED",
|
|
"OP_TYPE_POT_X",
|
|
"OP_TYPE_POT_Y"
|
|
#endif
|
|
};
|
|
|
|
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 shaderParms[MAX_ENTITY_SHADER_PARMS],
|
|
const viewDef_t * view, 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] = view->floatTime;
|
|
registers[EXP_REG_PARM0] = shaderParms[0];
|
|
registers[EXP_REG_PARM1] = shaderParms[1];
|
|
registers[EXP_REG_PARM2] = shaderParms[2];
|
|
registers[EXP_REG_PARM3] = shaderParms[3];
|
|
registers[EXP_REG_PARM4] = shaderParms[4];
|
|
registers[EXP_REG_PARM5] = shaderParms[5];
|
|
registers[EXP_REG_PARM6] = shaderParms[6];
|
|
registers[EXP_REG_PARM7] = shaderParms[7];
|
|
registers[EXP_REG_PARM8] = shaderParms[8];
|
|
registers[EXP_REG_PARM9] = shaderParms[9];
|
|
registers[EXP_REG_PARM10] = shaderParms[10];
|
|
registers[EXP_REG_PARM11] = shaderParms[11];
|
|
|
|
#ifdef PREY
|
|
// Prey has one extra entity shader parm copied into predefined register 21.
|
|
// Make sure EXP_REG_GLOBAL0 is defined as the correct predefined register index.
|
|
registers[EXP_REG_GLOBAL0] = shaderParms[12];
|
|
#else
|
|
registers[EXP_REG_GLOBAL0] = view->renderView.shaderParms[0];
|
|
#endif
|
|
|
|
registers[EXP_REG_GLOBAL1] = view->renderView.shaderParms[1];
|
|
registers[EXP_REG_GLOBAL2] = view->renderView.shaderParms[2];
|
|
registers[EXP_REG_GLOBAL3] = view->renderView.shaderParms[3];
|
|
registers[EXP_REG_GLOBAL4] = view->renderView.shaderParms[4];
|
|
registers[EXP_REG_GLOBAL5] = view->renderView.shaderParms[5];
|
|
registers[EXP_REG_GLOBAL6] = view->renderView.shaderParms[6];
|
|
registers[EXP_REG_GLOBAL7] = view->renderView.shaderParms[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:
|
|
#ifdef PREY
|
|
// Prey returns 0.0 when dividing by zero.
|
|
if (registers[op->b] == 0.0f) {
|
|
registers[op->c] = 0.0f;
|
|
}
|
|
else {
|
|
registers[op->c] = registers[op->a] / registers[op->b];
|
|
}
|
|
#else
|
|
registers[op->c] = registers[op->a] / registers[op->b];
|
|
#endif
|
|
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 (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;
|
|
|
|
#ifdef PREY
|
|
case OP_TYPE_FRAGMENTPROGRAMS:
|
|
registers[op->c] = glConfig.ARBFragmentProgramAvailable ? 1.0f : 0.0f;
|
|
break;
|
|
#endif
|
|
#ifdef QUAKE4
|
|
case OP_TYPE_GLSL_ENABLED:
|
|
registers[op->c] = glConfig.ARBFragmentProgramAvailable ? 1.0f : 0.0f;
|
|
break;
|
|
case OP_TYPE_POT_X:
|
|
registers[op->c] = glConfig.vidWidth > 0 ? (float)glConfig.vidWidth / (float)R_MaterialMakePowerOfTwo(glConfig.vidWidth) : 1.0f;
|
|
break;
|
|
case OP_TYPE_POT_Y:
|
|
registers[op->c] = glConfig.vidHeight > 0 ? (float)glConfig.vidHeight / (float)R_MaterialMakePowerOfTwo(glConfig.vidHeight) : 1.0f;
|
|
break;
|
|
#endif
|
|
|
|
default:
|
|
common->FatalError("R_EvaluateExpression: bad opcode");
|
|
}
|
|
}
|
|
}
|
|
|
|
#ifdef QUAKE4
|
|
/*
|
|
===============
|
|
idMaterial::EvaluateStageRegisters
|
|
===============
|
|
*/
|
|
void idMaterial::EvaluateStageRegisters(int stageIndex, float* registers, const float shaderParms[MAX_ENTITY_SHADER_PARMS], float floatTime) const {
|
|
if (stageIndex < 0 || stageIndex >= numStages) {
|
|
return;
|
|
}
|
|
|
|
for (int i = EXP_REG_NUM_PREDEFINED; i < numRegisters; i++) {
|
|
registers[i] = expressionRegisters[i];
|
|
}
|
|
registers[EXP_REG_TIME] = floatTime;
|
|
registers[EXP_REG_PARM0] = shaderParms[0];
|
|
registers[EXP_REG_PARM1] = shaderParms[1];
|
|
registers[EXP_REG_PARM2] = shaderParms[2];
|
|
registers[EXP_REG_PARM3] = shaderParms[3];
|
|
registers[EXP_REG_PARM4] = shaderParms[4];
|
|
registers[EXP_REG_PARM5] = shaderParms[5];
|
|
registers[EXP_REG_PARM6] = shaderParms[6];
|
|
registers[EXP_REG_PARM7] = shaderParms[7];
|
|
registers[EXP_REG_PARM8] = shaderParms[8];
|
|
registers[EXP_REG_PARM9] = shaderParms[9];
|
|
registers[EXP_REG_PARM10] = shaderParms[10];
|
|
registers[EXP_REG_PARM11] = shaderParms[11];
|
|
registers[EXP_REG_GLOBAL0] = 0.0f;
|
|
registers[EXP_REG_GLOBAL1] = 0.0f;
|
|
registers[EXP_REG_GLOBAL2] = 0.0f;
|
|
registers[EXP_REG_GLOBAL3] = 0.0f;
|
|
registers[EXP_REG_GLOBAL4] = 0.0f;
|
|
registers[EXP_REG_GLOBAL5] = 0.0f;
|
|
registers[EXP_REG_GLOBAL6] = 0.0f;
|
|
registers[EXP_REG_GLOBAL7] = 0.0f;
|
|
registers[EXP_REG_VERTEX_RANDOMIZER] = 0.0f;
|
|
|
|
const shaderStage_t* stage = &stages[stageIndex];
|
|
const int firstOp = stage->mStageOpsStart;
|
|
const int lastOp = firstOp + stage->mNumStageOps;
|
|
for (int i = firstOp; i < lastOp; i++) {
|
|
const expOp_t* op = &ops[i];
|
|
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->b] == 0.0f ? 0.0f : registers[op->a] / registers[op->b];
|
|
break;
|
|
case OP_TYPE_MOD: {
|
|
int 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_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;
|
|
case OP_TYPE_SOUND:
|
|
registers[op->c] = 0.0f;
|
|
break;
|
|
case OP_TYPE_GLSL_ENABLED:
|
|
registers[op->c] = glConfig.ARBFragmentProgramAvailable ? 1.0f : 0.0f;
|
|
break;
|
|
case OP_TYPE_POT_X:
|
|
registers[op->c] = glConfig.vidWidth > 0 ? (float)glConfig.vidWidth / (float)R_MaterialMakePowerOfTwo(glConfig.vidWidth) : 1.0f;
|
|
break;
|
|
case OP_TYPE_POT_Y:
|
|
registers[op->c] = glConfig.vidHeight > 0 ? (float)glConfig.vidHeight / (float)R_MaterialMakePowerOfTwo(glConfig.vidHeight) : 1.0f;
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
#endif
|
|
|
|
/*
|
|
=============
|
|
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(void) const {
|
|
assert(GetStage(0) && GetStage(0)->texture.image);
|
|
return GetStage(0)->texture.image->uploadWidth;
|
|
}
|
|
|
|
/*
|
|
=============
|
|
idMaterial::GetImageHeight
|
|
=============
|
|
*/
|
|
int idMaterial::GetImageHeight(void) const {
|
|
assert(GetStage(0) && GetStage(0)->texture.image);
|
|
return GetStage(0)->texture.image->uploadHeight;
|
|
}
|
|
|
|
/*
|
|
=============
|
|
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 {
|
|
if (!stages || !stages[0].texture.cinematic || !backEnd.viewDef) {
|
|
return;
|
|
}
|
|
stages[0].texture.cinematic->ImageForTime(tr.primaryRenderView.time);
|
|
}
|
|
|
|
/*
|
|
=============
|
|
idMaterial::CloseCinematic
|
|
=============
|
|
*/
|
|
void idMaterial::CloseCinematic(void) 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::ConstantRegisters
|
|
=============
|
|
*/
|
|
const float* idMaterial::ConstantRegisters() const {
|
|
if (!r_useConstantMaterials.GetBool()) {
|
|
return NULL;
|
|
}
|
|
return constantRegisters;
|
|
}
|
|
|
|
/*
|
|
==================
|
|
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.
|
|
This is probably an optimization of dubious value.
|
|
==================
|
|
*/
|
|
static int c_constant, c_variable;
|
|
void idMaterial::CheckForConstantRegisters() {
|
|
if (!pd->registersAreConstant) {
|
|
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));
|
|
|
|
#ifdef QUAKE4
|
|
EvaluateRegisters(constantRegisters, shaderParms, &viewDef, 0);
|
|
#else
|
|
EvaluateRegisters(constantRegisters, shaderParms, &viewDef, 0);
|
|
#endif
|
|
}
|
|
|
|
/*
|
|
===================
|
|
idMaterial::ImageName
|
|
===================
|
|
*/
|
|
const char* idMaterial::ImageName(void) const {
|
|
if (numStages == 0) {
|
|
return "_scratch";
|
|
}
|
|
idImage* image = stages[0].texture.image;
|
|
if (image) {
|
|
return image->imgName;
|
|
}
|
|
return "_scratch";
|
|
}
|
|
|
|
/*
|
|
===================
|
|
idMaterial::SetImageClassifications
|
|
|
|
Just for image resource tracking.
|
|
===================
|
|
*/
|
|
void idMaterial::SetImageClassifications(int tag) const {
|
|
for (int i = 0; i < numStages; i++) {
|
|
idImage* image = stages[i].texture.image;
|
|
if (image) {
|
|
image->SetClassification(tag);
|
|
}
|
|
}
|
|
}
|
|
|
|
/*
|
|
=================
|
|
idMaterial::Size
|
|
=================
|
|
*/
|
|
size_t idMaterial::Size(void) const {
|
|
return sizeof(idMaterial);
|
|
}
|
|
|
|
/*
|
|
===================
|
|
idMaterial::SetDefaultText
|
|
===================
|
|
*/
|
|
bool idMaterial::SetDefaultText(void) {
|
|
// 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(void) 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(false, force);
|
|
}
|
|
}
|
|
#if 0 // #ifdef QUAKE4
|
|
}
|
|
else if (stages[i].newShaderStage) {
|
|
for (int j = 0; j < MAX_FRAGMENT_IMAGES; j++) {
|
|
if (stages[i].newShaderStage->textureParmImages[j]) {
|
|
stages[i].newShaderStage->textureParmImages[j]->Reload(false, force);
|
|
}
|
|
}
|
|
#endif
|
|
}
|
|
else if (stages[i].texture.image) {
|
|
stages[i].texture.image->Reload(false, force);
|
|
}
|
|
}
|
|
}
|
|
|
|
/*
|
|
===============
|
|
idMaterial::LoadNeuralBytesForMaterial
|
|
===============
|
|
*/
|
|
void idMaterial::LoadNeuralBytesForMaterial(const char* name) {
|
|
if (neuralPOMBytesLoaded) {
|
|
return;
|
|
}
|
|
|
|
neuralPOMBytesLoaded = true;
|
|
neuralPOMHasData = false;
|
|
neuralPOMUploadedToShim = false;
|
|
|
|
neuralPOMWeightsBytes.Clear();
|
|
neuralPOMLatentRGBA16FBytes.Clear();
|
|
|
|
if (!name || !name[0]) {
|
|
return;
|
|
}
|
|
|
|
idStr materialPath = name;
|
|
materialPath.Replace("/", "__");
|
|
materialPath.Replace("\\", "__");
|
|
|
|
const char* weightsPath = va("neural/%s_weights.bin", materialPath.c_str());
|
|
const char* latentPath = va("neural/%s_latent_rgba16f.bin", materialPath.c_str());
|
|
|
|
void* weightsData = nullptr;
|
|
int weightsLen = fileSystem->ReadFile(weightsPath, &weightsData, nullptr);
|
|
|
|
void* latentData = nullptr;
|
|
int latentLen = fileSystem->ReadFile(latentPath, &latentData, nullptr);
|
|
|
|
if (weightsLen <= 0 || latentLen <= 0 || !weightsData || !latentData) {
|
|
if (weightsData) {
|
|
fileSystem->FreeFile(weightsData);
|
|
}
|
|
if (latentData) {
|
|
fileSystem->FreeFile(latentData);
|
|
}
|
|
|
|
neuralPOMHasData = false;
|
|
return;
|
|
}
|
|
|
|
neuralPOMWeightsBytes.SetNum(weightsLen);
|
|
memcpy(neuralPOMWeightsBytes.Ptr(), weightsData, weightsLen);
|
|
|
|
neuralPOMLatentRGBA16FBytes.SetNum(latentLen);
|
|
memcpy(neuralPOMLatentRGBA16FBytes.Ptr(), latentData, latentLen);
|
|
|
|
fileSystem->FreeFile(weightsData);
|
|
fileSystem->FreeFile(latentData);
|
|
|
|
neuralPOMHasData = true;
|
|
|
|
// Upload to shim immediately.
|
|
// This still keeps inference GPU-only. The CPU is only passing opaque bytes
|
|
// to the D3D12 shim, like a texture upload.
|
|
if (neuralPOMGLHandle == 0) {
|
|
glGenTextures(1, &neuralPOMGLHandle);
|
|
}
|
|
|
|
if (neuralPOMGLHandle != 0) {
|
|
glNeuralPOMMaterialQD3D12(
|
|
neuralPOMGLHandle,
|
|
neuralPOMWeightsBytes.Num(),
|
|
neuralPOMWeightsBytes.Ptr(),
|
|
neuralPOMLatentRGBA16FBytes.Num(),
|
|
neuralPOMLatentRGBA16FBytes.Ptr()
|
|
);
|
|
|
|
neuralPOMUploadedToShim = true;
|
|
}
|
|
}
|
|
|
|
#ifdef QUAKE4
|
|
/*
|
|
===============
|
|
rvMaterialEditLocal
|
|
===============
|
|
*/
|
|
class rvMaterialEditLocal : public rvMaterialEdit {
|
|
public:
|
|
virtual void SetGui(idMaterial* edit, const char* name) {
|
|
edit->SetGui(name);
|
|
}
|
|
virtual int GetImageWidth(const idMaterial* edit) const {
|
|
return edit->GetImageWidth();
|
|
}
|
|
virtual int GetImageHeight(const idMaterial* edit) const {
|
|
return edit->GetImageHeight();
|
|
}
|
|
};
|
|
|
|
static rvMaterialEditLocal materialEditLocal;
|
|
rvMaterialEdit* materialEdit = &materialEditLocal;
|
|
#endif
|
|
|