// Copyright (C) 2006-2009 Nikolaus Gebhardt / Thomas Alten // This file is part of the "Irrlicht Engine". // For conditions of distribution and use, see copyright notice in irrlicht.h #ifndef __I_Q3_LEVEL_SHADER_H_INCLUDED__ #define __I_Q3_LEVEL_SHADER_H_INCLUDED__ #include "irrArray.h" #include "fast_atof.h" #include "IFileSystem.h" #include "IVideoDriver.h" #include "coreutil.h" namespace irr { namespace scene { namespace quake3 { static const core::stringc irrEmptyStringc(""); //! Hold the different Mesh Types used for getMesh enum eQ3MeshIndex { E_Q3_MESH_GEOMETRY = 0, E_Q3_MESH_ITEMS, E_Q3_MESH_BILLBOARD, E_Q3_MESH_SIZE }; // we are not using gamma, so quake3 is very dark. // define the standard multiplication for lightmaps and vertex colors const video::E_MATERIAL_TYPE defaultMaterialType = video::EMT_LIGHTMAP_M4; const video::E_MODULATE_FUNC defaultModulate = video::EMFN_MODULATE_4X; // some useful typedefs typedef core::array< core::stringc > tStringList; typedef core::array< video::ITexture* > tTexArray; // name = "a b c .." struct SVariable { core::stringc name; core::stringc content; void clear () { name = ""; content = ""; } s32 isValid () const { return name.size(); } bool operator == ( const SVariable &other ) const { return name == other.name; } }; // string helper.. TODO: move to generic files inline s32 isEqual ( const core::stringc &string, u32 &pos, const c8 *list[], u32 listSize ) { const char * in = string.c_str () + pos; for ( u32 i = 0; i != listSize; ++i ) { if (string.size() < pos) return -2; u32 len = (u32) strlen ( list[i] ); if (string.size() < pos+len) continue; if ( in [len] != 0 && in [len] != ' ' ) continue; if ( strncmp ( in, list[i], len ) ) continue; pos += len + 1; return (s32) i; } return -2; } inline f32 getAsFloat ( const core::stringc &string, u32 &pos ) { const char * in = string.c_str () + pos; f32 value = 0.f; pos += (u32) ( core::fast_atof_move ( in, value ) - in ) + 1; return value; } inline core::vector3df getAsVector3df ( const core::stringc &string, u32 &pos ) { core::vector3df v; v.X = getAsFloat ( string, pos ); v.Z = getAsFloat ( string, pos ); v.Y = getAsFloat ( string, pos ); return v; } /* extract substrings */ inline void getAsStringList ( tStringList &list, s32 max, const core::stringc &string, u32 &startPos ) { list.clear (); s32 finish = 0; s32 endPos; do { endPos = string.findNext ( ' ', startPos ); if ( endPos == -1 ) { finish = 1; endPos = string.size(); } list.push_back ( string.subString ( startPos, endPos - startPos ) ); startPos = endPos + 1; if ( list.size() >= (u32) max ) finish = 1; } while ( !finish ); } //! A blend function for a q3 shader. struct SBlendFunc { SBlendFunc () : type ( video::EMT_SOLID ), modulate ( defaultModulate ), param ( 0.f ), isTransparent ( false ) {} video::E_MATERIAL_TYPE type; video::E_MODULATE_FUNC modulate; f32 param; bool isTransparent; }; // parses the content of Variable cull inline bool isDisabled ( const core::stringc &string ) { if ( string.size() == 0 ) return true; bool ret = true; static const c8 * funclist[] = { "none", "disable" }; u32 pos = 0; switch ( isEqual ( string, pos, funclist, 2 ) ) { case 0: case 1: ret = false; break; } return ret; } // parses the content of Variable depthfunc // return a z-test inline u32 getDepthFunction ( const core::stringc &string ) { if ( string.size() == 0 ) return 1; u32 ret = 1; static const c8 * funclist[] = { "lequal","equal" }; u32 pos = 0; switch ( isEqual ( string, pos, funclist, 2 ) ) { case 0: ret = 1; case 1: ret = 2; break; } return ret; } // parses the content of Variable blendfunc,alphafunc inline static void getBlendFunc ( const core::stringc &string, SBlendFunc &blendfunc ) { if ( string.size() == 0 ) return; // maps to E_BLEND_FACTOR static const c8 * funclist[] = { "gl_zero", "gl_one", "gl_dst_color", "gl_one_minus_dst_color", "gl_src_color", "gl_one_minus_src_color", "gl_src_alpha", "gl_one_minus_src_alpha", "gl_dst_alpha", "gl_one_minus_dst_alpha", "gl_src_alpha_sat", "add", "filter", "blend", "ge128", "gt0" }; u32 pos = 0; s32 srcFact = isEqual ( string, pos, funclist, 16 ); if ( srcFact < 0 ) return; u32 resolved = 0; s32 dstFact = isEqual ( string, pos, funclist, 16 ); switch ( srcFact ) { case video::EBF_ONE: switch ( dstFact ) { // gl_one gl_zero case video::EBF_ZERO: blendfunc.type = video::EMT_SOLID; blendfunc.isTransparent = false; resolved = 1; break; // gl_one gl_one case video::EBF_ONE: blendfunc.type = video::EMT_TRANSPARENT_ADD_COLOR; blendfunc.isTransparent = true; resolved = 1; break; } break; case video::EBF_SRC_ALPHA: switch ( dstFact ) { // gl_src_alpha gl_one_minus_src_alpha case video::EBF_ONE_MINUS_SRC_ALPHA: blendfunc.type = video::EMT_TRANSPARENT_ALPHA_CHANNEL; blendfunc.param = 1.f / 255.f; blendfunc.isTransparent = true; resolved = 1; break; } break; case 11: // add blendfunc.type = video::EMT_TRANSPARENT_ADD_COLOR; blendfunc.isTransparent = true; resolved = 1; break; case 12: // filter = gl_dst_color gl_zero blendfunc.type = video::EMT_ONETEXTURE_BLEND; blendfunc.param = video::pack_texureBlendFunc ( video::EBF_DST_COLOR, video::EBF_ZERO, defaultModulate ); blendfunc.isTransparent = false; resolved = 1; break; case 13: // blend blendfunc.type = video::EMT_TRANSPARENT_ALPHA_CHANNEL; blendfunc.param = 1.f / 255.f; blendfunc.isTransparent = true; resolved = 1; break; case 14: // alphafunc ge128 blendfunc.type = video::EMT_TRANSPARENT_ALPHA_CHANNEL_REF; blendfunc.param = 0.5f; blendfunc.isTransparent = true; resolved = 1; break; case 15: // alphafunc gt0 blendfunc.type = video::EMT_TRANSPARENT_ALPHA_CHANNEL_REF; blendfunc.param = 1.f / 255.f; blendfunc.isTransparent = true; resolved = 1; break; } // use the generic blender if ( 0 == resolved ) { blendfunc.type = video::EMT_ONETEXTURE_BLEND; blendfunc.param = video::pack_texureBlendFunc ( (video::E_BLEND_FACTOR) srcFact, (video::E_BLEND_FACTOR) dstFact, blendfunc.modulate); if (srcFact == video::EBF_SRC_COLOR && dstFact == video::EBF_ZERO) { blendfunc.isTransparent = 0; } else { blendfunc.isTransparent = true; } } } struct SModifierFunction { SModifierFunction () : masterfunc0 ( -2 ), masterfunc1(0), func ( 0 ), tcgen( 8 ), base ( 0 ), amp ( 1 ), phase ( 0 ), freq ( 1 ), wave(1) {} // "tcmod","deformvertexes","rgbgen", "tcgen" s32 masterfunc0; // depends s32 masterfunc1; // depends s32 func; s32 tcgen; union { f32 base; f32 bulgewidth; }; union { f32 amp; f32 bulgeheight; }; f32 phase; union { f32 freq; f32 bulgespeed; }; f32 wave; f32 evaluate ( f32 dt ) const { // phase in 0 and 1.. f32 x = core::fract( (dt + phase ) * freq ); f32 y = 0.f; switch ( func ) { // sin case 0: y = (f32) sin ( x * core::PI64 * 2.0 ); break; // cos case 1: y = (f32) cos ( x * core::PI64 * 2.0 ); break; // square case 2: y = x < 0.5f ? 1.f : -1.f; break; // triangle case 3: y = x < 0.5f ? ( 2.f * x ) - 1.f : ( -2.f * x ) + 2.f; break; // sawtooth: case 4: y = x; break; // inverse sawtooth: case 5: y = 1.f - x; break; } return base + ( y * amp ); } }; // inline void getModifierFunc ( SModifierFunction& fill, const core::stringc &string, u32 &pos ) { if ( string.size() == 0 ) return; static const c8 * funclist[] = { "sin","cos","square", "triangle", "sawtooth","inversesawtooth" }; fill.func = quake3::isEqual ( string,pos, funclist,6 ); if ( fill.func == -2 ) fill.func = 0; fill.base = quake3::getAsFloat ( string, pos ); fill.amp = quake3::getAsFloat ( string, pos ); fill.phase = quake3::getAsFloat ( string, pos ); fill.freq = quake3::getAsFloat ( string, pos ); } struct SVarGroup { // simple assoziative array s32 getIndex( const c8 * name ) const { SVariable search; search.name = name; return Variable.linear_search ( search ); } // searches for Variable name and returns is content // if Variable is not found a reference to an Empty String is returned const core::stringc &get( const c8 * name ) const { s32 index = getIndex ( name ); if ( index < 0 ) return irrEmptyStringc; return Variable [ index ].content; } bool isDefined ( const c8 * name, const c8 * content = 0 ) const { for ( u32 i = 0; i != Variable.size (); ++i ) { if ( 0 == strcmp ( Variable[i].name.c_str(), name ) ) { if ( 0 == content ) return true; if ( 0 == strcmp ( Variable[i].content.c_str(), content ) ) return true; } } return false; } core::array < SVariable > Variable; }; struct SVarGroupList: public IReferenceCounted { SVarGroupList () {} virtual ~SVarGroupList () {} core::array < SVarGroup > VariableGroup; }; //! A Parsed Shader Holding Variables ordered in Groups class SShader { public: bool operator == (const SShader &other ) const { return name == other.name; } bool operator < (const SShader &other ) const { return name < other.name; } const SVarGroup * getGroup ( u32 stage ) const { if ( 0 == VarGroup || stage >= VarGroup->VariableGroup.size () ) return 0; return &VarGroup->VariableGroup [ stage ]; } // id s32 id; // Shader: shader name ( also first variable in first Vargroup ) // Entity: classname ( variable in Group(1) ) core::stringc name; SVarGroupList *VarGroup; // reference }; typedef SShader SEntity; typedef core::array < SEntity > tQ3EntityList; /* dump shader like original layout, regardless of internal data holding no recursive folding.. */ inline void dumpVarGroup ( core::stringc &dest, const SVarGroup * group, s32 stack ) { core::stringc buf; s32 i; if ( stack > 0 ) { buf = ""; for ( i = 0; i < stack - 1; ++i ) buf += '\t'; buf += "{\n"; dest.append ( buf ); } for ( u32 g = 0; g != group->Variable.size(); ++g ) { buf = ""; for ( i = 0; i < stack; ++i ) buf += '\t'; buf += group->Variable[g].name; buf += " "; buf += group->Variable[g].content; buf += "\n"; dest.append ( buf ); } if ( stack > 1 ) { buf = ""; for ( i = 0; i < stack - 1; ++i ) buf += '\t'; buf += "}\n"; dest.append ( buf ); } } inline core::stringc & dumpShader ( core::stringc &dest, const SShader * shader ) { dest = ""; if ( 0 == shader ) return dest; const SVarGroup * group; const u32 size = shader->VarGroup->VariableGroup.size (); for ( u32 i = 0; i != size; ++i ) { group = &shader->VarGroup->VariableGroup[ i ]; dumpVarGroup ( dest, group, core::clamp( (int)i, 0, 2 ) ); } if ( size <= 1 ) { dest.append ( "{\n" ); } dest.append ( "}\n" ); return dest; } /* quake3 doesn't care much about tga & jpg load one or multiple files stored in name started at startPos to the texture array textures if texture is not loaded 0 will be added ( to find missing textures easier) */ inline void getTextures(tTexArray &textures, const core::stringc &name, u32 &startPos, io::IFileSystem *fileSystem, video::IVideoDriver* driver) { static const char * extension[2] = { ".jpg", ".tga" }; tStringList stringList; getAsStringList ( stringList, -1, name, startPos ); textures.clear(); core::stringc loadFile; for ( u32 i = 0; i!= stringList.size (); ++i ) { video::ITexture* texture = 0; for ( u32 g = 0; g != 2 ; ++g ) { core::cutFilenameExtension ( loadFile, stringList[i] ).append ( extension[g] ); if ( fileSystem->existFile ( loadFile.c_str() ) ) { texture = driver->getTexture( loadFile.c_str () ); if ( texture ) { break; } } } // take 0 Texture textures.push_back(texture); } } //! Manages various Quake3 Shader Styles class IShaderManager : public IReferenceCounted { }; } // end namespace quake3 } // end namespace scene } // end namespace irr #endif