From 5d5e878129be994d435ee4bcf3c513b8a76f8420 Mon Sep 17 00:00:00 2001 From: Azaezel Date: Thu, 23 Oct 2014 20:48:58 -0500 Subject: [PATCH 001/109] dynamic cubemap support for tsStatics engine: implements the capacity for tsstatic objects to allow themselves to use the reflector system in order to display runtime generated cubemaps. script: defines a calibrated reflectordesc that reflects all object types within the space of approximately half stock veiwdistance usage: cubeReflectorDesc = DefaultCubeDesc placed in a given object-insatnces entry that uses a material with a dynamiccubemap = true; flag. immediate purpose: consistency of application of materials. long term impact: tags steps required in order to associate a given runtime generated cubemap with an object-instance. in the future, this is likely to give way to an area-derived cubemap based on the accumulation volume tech developed by Andrew Mac, or a screenspace reflection. --- Engine/source/T3D/tsStatic.cpp | 60 +++++++++++++++++++ Engine/source/T3D/tsStatic.h | 9 +++ .../Full/game/art/datablocks/environment.cs | 12 ++++ 3 files changed, 81 insertions(+) diff --git a/Engine/source/T3D/tsStatic.cpp b/Engine/source/T3D/tsStatic.cpp index 1474bf96d..c7ef76d4f 100644 --- a/Engine/source/T3D/tsStatic.cpp +++ b/Engine/source/T3D/tsStatic.cpp @@ -90,6 +90,9 @@ ConsoleDocClass( TSStatic, ); TSStatic::TSStatic() +: + cubeDescId( 0 ), + reflectorDesc( NULL ) { mNetFlags.set(Ghostable | ScopeAlways); @@ -180,6 +183,11 @@ void TSStatic::initPersistFields() endGroup("Rendering"); + addGroup( "Reflection" ); + addField( "cubeReflectorDesc", TypeRealString, Offset( cubeDescName, TSStatic ), + "References a ReflectorDesc datablock that defines performance and quality properties for dynamic reflections.\n"); + endGroup( "Reflection" ); + addGroup("Collision"); addField( "collisionType", TypeTSMeshType, Offset( mCollisionType, TSStatic ), @@ -279,6 +287,14 @@ bool TSStatic::onAdd() addToScene(); + if ( isClientObject() ) + { + mCubeReflector.unregisterReflector(); + + if ( reflectorDesc ) + mCubeReflector.registerReflector( this, reflectorDesc ); + } + _updateShouldTick(); return true; @@ -337,6 +353,16 @@ bool TSStatic::_createShape() if ( mAmbientThread ) mShapeInstance->setSequence( mAmbientThread, ambientSeq, 0); + // Resolve CubeReflectorDesc. + if ( cubeDescName.isNotEmpty() ) + { + Sim::findObject( cubeDescName, reflectorDesc ); + } + else if( cubeDescId > 0 ) + { + Sim::findObject( cubeDescId, reflectorDesc ); + } + return true; } @@ -402,6 +428,8 @@ void TSStatic::onRemove() mShapeInstance = NULL; mAmbientThread = NULL; + if ( isClientObject() ) + mCubeReflector.unregisterReflector(); Parent::onRemove(); } @@ -504,6 +532,12 @@ void TSStatic::prepRenderImage( SceneRenderState* state ) F32 invScale = (1.0f/getMax(getMax(mObjScale.x,mObjScale.y),mObjScale.z)); + // If we're currently rendering our own reflection we + // don't want to render ourselves into it. + if ( mCubeReflector.isRendering() ) + return; + + if ( mForceDetail == -1 ) mShapeInstance->setDetailFromDistance( state, dist * invScale ); else @@ -520,6 +554,9 @@ void TSStatic::prepRenderImage( SceneRenderState* state ) rdata.setFadeOverride( 1.0f ); rdata.setOriginSort( mUseOriginSort ); + if ( mCubeReflector.isEnabled() ) + rdata.setCubemap( mCubeReflector.getCubemap() ); + // If we have submesh culling enabled then prepare // the object space frustum to pass to the shape. Frustum culler; @@ -544,6 +581,20 @@ void TSStatic::prepRenderImage( SceneRenderState* state ) mat.scale( mObjScale ); GFX->setWorldMatrix( mat ); + if ( state->isDiffusePass() && mCubeReflector.isEnabled() && mCubeReflector.getOcclusionQuery() ) + { + RenderPassManager *pass = state->getRenderPass(); + OccluderRenderInst *ri = pass->allocInst(); + + ri->type = RenderPassManager::RIT_Occluder; + ri->query = mCubeReflector.getOcclusionQuery(); + mObjToWorld.mulP( mObjBox.getCenter(), &ri->position ); + ri->scale.set( mObjBox.getExtents() ); + ri->orientation = pass->allocUniqueXform( mObjToWorld ); + ri->isSphere = false; + state->getRenderPass()->addInst( ri ); + } + mShapeInstance->animate(); mShapeInstance->render( rdata ); @@ -628,6 +679,10 @@ U32 TSStatic::packUpdate(NetConnection *con, U32 mask, BitStream *stream) if ( mLightPlugin ) retMask |= mLightPlugin->packUpdate(this, AdvancedStaticOptionsMask, con, mask, stream); + if( stream->writeFlag( reflectorDesc != NULL ) ) + { + stream->writeRangedU32( reflectorDesc->getId(), DataBlockObjectIdFirst, DataBlockObjectIdLast ); + } return retMask; } @@ -687,6 +742,11 @@ void TSStatic::unpackUpdate(NetConnection *con, BitStream *stream) mLightPlugin->unpackUpdate(this, con, stream); } + if( stream->readFlag() ) + { + cubeDescId = stream->readRangedU32( DataBlockObjectIdFirst, DataBlockObjectIdLast ); + } + if ( isProperlyAdded() ) _updateShouldTick(); } diff --git a/Engine/source/T3D/tsStatic.h b/Engine/source/T3D/tsStatic.h index fe7113294..ac6c50cc1 100644 --- a/Engine/source/T3D/tsStatic.h +++ b/Engine/source/T3D/tsStatic.h @@ -39,6 +39,10 @@ #include "ts/tsShape.h" #endif +#ifndef _REFLECTOR_H_ + #include "scene/reflector.h" +#endif + class TSShapeInstance; class TSThread; class TSStatic; @@ -135,6 +139,11 @@ protected: /// Start or stop processing ticks depending on our state. void _updateShouldTick(); + String cubeDescName; + U32 cubeDescId; + ReflectorDesc *reflectorDesc; + CubeReflector mCubeReflector; + protected: Convex *mConvexList; diff --git a/Templates/Full/game/art/datablocks/environment.cs b/Templates/Full/game/art/datablocks/environment.cs index 386703f67..c9d2b0a51 100644 --- a/Templates/Full/game/art/datablocks/environment.cs +++ b/Templates/Full/game/art/datablocks/environment.cs @@ -84,3 +84,15 @@ datablock LightningData(DefaultStorm) thunderSounds[2] = ThunderCrash3Sound; thunderSounds[3] = ThunderCrash4Sound; }; + +datablock ReflectorDesc( DefaultCubeDesc ) +{ + texSize = 256; + nearDist = 0.1; + farDist = 1000.0; + objectTypeMask = 0xFFFFFFFF; + detailAdjust = 1.0; + priority = 1.0; + maxRateMs = 15; + useOcclusionQuery = true; +}; From b8c750dd56056edf4df47d1540cb103c775af037 Mon Sep 17 00:00:00 2001 From: Azaezel Date: Sun, 11 Jan 2015 17:45:29 -0600 Subject: [PATCH 002/109] extraneous mipmap generation prune OR spits out quite a few pointless mips for non square textures, which can lead to it triggering that AssertFatal(mNumMipLevels <= c_maxMipLevels, "GBitmap::allocateBitmap: too many miplevels"); entry --- Engine/source/gfx/bitmap/gBitmap.cpp | 2 +- Engine/source/gfx/gfxTextureManager.cpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Engine/source/gfx/bitmap/gBitmap.cpp b/Engine/source/gfx/bitmap/gBitmap.cpp index 151a792ed..78454d5e3 100644 --- a/Engine/source/gfx/bitmap/gBitmap.cpp +++ b/Engine/source/gfx/bitmap/gBitmap.cpp @@ -326,7 +326,7 @@ void GBitmap::allocateBitmap(const U32 in_width, const U32 in_height, const bool mNumMipLevels++; allocPixels += currWidth * currHeight * mBytesPerPixel; - } while (currWidth != 1 || currHeight != 1); + } while (currWidth != 1 && currHeight != 1); } AssertFatal(mNumMipLevels <= c_maxMipLevels, "GBitmap::allocateBitmap: too many miplevels"); diff --git a/Engine/source/gfx/gfxTextureManager.cpp b/Engine/source/gfx/gfxTextureManager.cpp index c997d1d2b..da132d7d2 100644 --- a/Engine/source/gfx/gfxTextureManager.cpp +++ b/Engine/source/gfx/gfxTextureManager.cpp @@ -1098,7 +1098,7 @@ void GFXTextureManager::_validateTexParams( const U32 width, const U32 height, currHeight = 1; inOutNumMips++; - } while ( currWidth != 1 || currHeight != 1 ); + } while ( currWidth != 1 && currHeight != 1 ); } } } From 39d5563a8c543e11fb1232d9b70fffcc0c993bd5 Mon Sep 17 00:00:00 2001 From: Nathan Bowhay Date: Thu, 29 Jan 2015 11:40:45 -0800 Subject: [PATCH 003/109] Added type checking functions added some type checking functions to TorqueScript. --- Engine/source/console/consoleFunctions.cpp | 200 +++++++++++++++++++++ Engine/source/console/consoleFunctions.h | 16 ++ 2 files changed, 216 insertions(+) create mode 100644 Engine/source/console/consoleFunctions.h diff --git a/Engine/source/console/consoleFunctions.cpp b/Engine/source/console/consoleFunctions.cpp index a5ea49f33..8d54ae076 100644 --- a/Engine/source/console/consoleFunctions.cpp +++ b/Engine/source/console/consoleFunctions.cpp @@ -25,6 +25,11 @@ #include "console/consoleInternal.h" #include "console/engineAPI.h" #include "console/ast.h" + +#ifndef _CONSOLFUNCTIONS_H_ +#include "console/consoleFunctions.h" +#endif + #include "core/strings/findMatch.h" #include "core/strings/stringUnit.h" #include "core/strings/unicode.h" @@ -45,6 +50,132 @@ bool LinkConsoleFunctions = false; // Buffer for expanding script filenames. static char scriptFilenameBuffer[1024]; +bool isInt(const char* str) +{ + int len = dStrlen(str); + if(len <= 0) + return false; + + // Ingore whitespace + int start = 0; + for(int i = start; i < len; i++) + if(str[i] != ' ') + { + start = i; + break; + } + + for(int i = start; i < len; i++) + switch(str[i]) + { + case '+': case '-': + if(i != 0) + return false; + break; + case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9': case '0': + break; + case ' ': // ignore whitespace + for(int j = i+1; j < len; j++) + if(str[j] != ' ') + return false; + return true; + break; + default: + return false; + } + return true; +} + +bool isFloat(const char* str, bool sciOk = false) +{ + int len = dStrlen(str); + if(len <= 0) + return false; + + // Ingore whitespace + int start = 0; + for(int i = start; i < len; i++) + if(str[i] != ' ') + { + start = i; + break; + } + + bool seenDot = false; + int eLoc = -1; + for(int i = 0; i < len; i++) + switch(str[i]) + { + case '+': case '-': + if(sciOk) + { + //Haven't found e or scientific notation symbol + if(eLoc == -1) + { + //only allowed in beginning + if(i != 0) + return false; + } + else + { + //if not right after the e + if(i != (eLoc + 1)) + return false; + } + } + else + { + //only allowed in beginning + if(i != 0) + return false; + } + break; + case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9': case '0': + break; + case 'e': case 'E': + if(!sciOk) + return false; + else + { + //already saw it so can't have 2 + if(eLoc != -1) + return false; + + eLoc = i; + } + break; + case '.': + if(seenDot | (sciOk && eLoc != -1)) + return false; + seenDot = true; + break; + case ' ': // ignore whitespace + for(int j = i+1; j < len; j++) + if(str[j] != ' ') + return false; + return true; + break; + default: + return false; + } + return true; +} + +bool isValidIP(const char* ip) +{ + unsigned b1, b2, b3, b4; + unsigned char c; + int rc = dSscanf(ip, "%3u.%3u.%3u.%3u%c", &b1, &b2, &b3, &b4, &c); + if (rc != 4 && rc != 5) return false; + if ((b1 | b2 | b3 | b4) > 255) return false; + if (dStrspn(ip, "0123456789.") < dStrlen(ip)) return false; + return true; +} + +bool isValidPort(U16 port) +{ + return (port >= 0 && port <=65535); +} //============================================================================= // String Functions. @@ -815,6 +946,75 @@ DefineConsoleFunction( strrchrpos, S32, ( const char* str, const char* chr, S32 return index; } +//---------------------------------------------------------------- + +// Warning: isInt and isFloat are very 'strict' and might need to be adjusted to allow other values. //seanmc +DefineConsoleFunction( isInt, bool, ( const char* str),, + "Returns true if the string is an integer.\n" + "@param str The string to test.\n" + "@return true if @a str is an integer and false if not\n\n" + "@tsexample\n" + "isInt( \"13\" ) // Returns true.\n" + "@endtsexample\n" + "@ingroup Strings" ) +{ + return isInt(str); +} + +//---------------------------------------------------------------- + +DefineConsoleFunction( isFloat, bool, ( const char* str, bool sciOk), (false), + "Returns true if the string is a float.\n" + "@param str The string to test.\n" + "@param sciOk Test for correct scientific notation and accept it (ex. 1.2e+14)" + "@return true if @a str is a float and false if not\n\n" + "@tsexample\n" + "isFloat( \"13.5\" ) // Returns true.\n" + "@endtsexample\n" + "@ingroup Strings" ) +{ + return isFloat(str, sciOk); +} + +//---------------------------------------------------------------- + +DefineConsoleFunction( isValidPort, bool, ( const char* str),, + "Returns true if the string is a valid port number.\n" + "@param str The string to test.\n" + "@return true if @a str is a port and false if not\n\n" + "@tsexample\n" + "isValidPort( \"8080\" ) // Returns true.\n" + "@endtsexample\n" + "@ingroup Strings" ) +{ + if(isInt(str)) + { + U16 port = dAtous(str); + return isValidPort(port); + } + else + return false; +} + +//---------------------------------------------------------------- + +DefineConsoleFunction( isValidIP, bool, ( const char* str),, + "Returns true if the string is a valid ip address, excepts localhost.\n" + "@param str The string to test.\n" + "@return true if @a str is a valid ip address and false if not\n\n" + "@tsexample\n" + "isValidIP( \"localhost\" ) // Returns true.\n" + "@endtsexample\n" + "@ingroup Strings" ) +{ + if(dStrcmp(str, "localhost") == 0) + { + return true; + } + else + return isValidIP(str); +} + //============================================================================= // Field Manipulators. //============================================================================= diff --git a/Engine/source/console/consoleFunctions.h b/Engine/source/console/consoleFunctions.h new file mode 100644 index 000000000..1cb19d1df --- /dev/null +++ b/Engine/source/console/consoleFunctions.h @@ -0,0 +1,16 @@ +#ifndef _CONSOLFUNCTIONS_H_ +#define _CONSOLFUNCTIONS_H_ + +#ifndef _STRINGFUNCTIONS_H_ +#include "core/strings/stringFunctions.h" +#endif + +bool isInt(const char* str); + +bool isFloat(const char* str); + +bool isValidIP(const char* ip); + +bool isValidPort(U16 port); + +#endif \ No newline at end of file From 14037a742a41eba97b65f01cb9094c1e60291f19 Mon Sep 17 00:00:00 2001 From: Nathan Bowhay Date: Thu, 29 Jan 2015 11:48:15 -0800 Subject: [PATCH 004/109] Added string manipulation functions added some string manipulation functions, some are slightly different versions of existing functions. --- Engine/source/console/consoleFunctions.cpp | 97 ++++++++++++++++++++++ 1 file changed, 97 insertions(+) diff --git a/Engine/source/console/consoleFunctions.cpp b/Engine/source/console/consoleFunctions.cpp index 8d54ae076..47a57ecdb 100644 --- a/Engine/source/console/consoleFunctions.cpp +++ b/Engine/source/console/consoleFunctions.cpp @@ -370,6 +370,40 @@ DefineConsoleFunction( strlen, S32, ( const char* str ),, return dStrlen( str ); } +//----------------------------------------------------------------------------- +DefineConsoleFunction( strlenskip, S32, ( const char* str, const char* first, const char* last ),, + "Calculate the length of a string in characters, skipping everything between and including first and last.\n" + "@param str A string.\n" + "@param first First character to look for to skip block of text.\n" + "@param last Second character to look for to skip block of text.\n" + "@return The length of the given string skipping blocks of text between characters.\n" + "@ingroup Strings" ) +{ + const UTF8* pos = str; + U32 size = 0; + U32 length = dStrlen(str); + bool count = true; + + //loop through each character counting each character, skipping tags (anything with < followed by >) + for(U32 i = 0; i < length; i++, pos++) + { + if(count) + { + if(*pos == first[0]) + count = false; + else + size++; + } + else + { + if(*pos == last[0]) + count = true; + } + } + + return S32(size); +} + //----------------------------------------------------------------------------- DefineConsoleFunction( strstr, S32, ( const char* string, const char* substring ),, @@ -416,6 +450,33 @@ DefineConsoleFunction( strpos, S32, ( const char* haystack, const char* needle, //----------------------------------------------------------------------------- +DefineConsoleFunction( strposr, S32, ( const char* haystack, const char* needle, S32 offset ), ( 0 ), + "Find the start of @a needle in @a haystack searching from right to left beginning at the given offset.\n" + "@param haystack The string to search.\n" + "@param needle The string to search for.\n" + "@return The index at which the first occurrence of @a needle was found in @a heystack or -1 if no match was found.\n\n" + "@tsexample\n" + "strposr( \"b ab\", \"b\", 1 ) // Returns 2.\n" + "@endtsexample\n" + "@ingroup Strings" ) +{ + U32 sublen = dStrlen( needle ); + U32 strlen = dStrlen( haystack ); + S32 start = strlen - offset; + + if(start < 0 || start > strlen) + return -1; + + if (start + sublen > strlen) + start = strlen - sublen; + for(; start >= 0; start--) + if(!dStrncmp(haystack + start, needle, sublen)) + return start; + return -1; +} + +//----------------------------------------------------------------------------- + DefineConsoleFunction( ltrim, const char*, ( const char* str ),, "Remove leading whitespace from the string.\n" "@param str A string.\n" @@ -762,6 +823,18 @@ DefineConsoleFunction( stripTrailingNumber, String, ( const char* str ),, return String::GetTrailingNumber( str, suffix ); } +//----------------------------------------------------------------------------- + +DefineConsoleFunction( getFirstNumber, String, ( const char* str ),, + "Get the first occuring number from @a str.\n" + "@param str The string from which to read out the first number.\n" + "@return String representation of the number or "" if no number.\n\n") +{ + U32 start; + U32 end; + return String::GetFirstNumber(str, start, end); +} + //---------------------------------------------------------------- DefineConsoleFunction( isspace, bool, ( const char* str, S32 index ),, @@ -948,6 +1021,30 @@ DefineConsoleFunction( strrchrpos, S32, ( const char* str, const char* chr, S32 //---------------------------------------------------------------- +DefineConsoleFunction( strToggleCaseToWords, const char*, ( const char* str ),, + "Parse a Toggle Case word into separate words.\n" + "@param str The string to parse.\n" + "@return new string space separated.\n\n" + "@tsexample\n" + "strToggleCaseToWords( \"HelloWorld\" ) // Returns \"Hello World\".\n" + "@endtsexample\n" + "@ingroup Strings" ) +{ + String newStr; + for(S32 i = 0; str[i]; i++) + { + //If capitol add a space + if(i != 0 && str[i] >= 65 && str[i] <= 90) + newStr += " "; + + newStr += str[i]; + } + + return Con::getReturnBuffer(newStr); +} + +//---------------------------------------------------------------- + // Warning: isInt and isFloat are very 'strict' and might need to be adjusted to allow other values. //seanmc DefineConsoleFunction( isInt, bool, ( const char* str),, "Returns true if the string is an integer.\n" From 2d7472d16067f1a03c3d76bfa05e6b7871042549 Mon Sep 17 00:00:00 2001 From: Nathan Bowhay Date: Thu, 29 Jan 2015 11:51:05 -0800 Subject: [PATCH 005/109] add case sensitive strings Added case sensitive strings function to add them to the string table. --- Engine/source/console/consoleFunctions.cpp | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/Engine/source/console/consoleFunctions.cpp b/Engine/source/console/consoleFunctions.cpp index 47a57ecdb..f08ad2ac4 100644 --- a/Engine/source/console/consoleFunctions.cpp +++ b/Engine/source/console/consoleFunctions.cpp @@ -1112,6 +1112,17 @@ DefineConsoleFunction( isValidIP, bool, ( const char* str),, return isValidIP(str); } +//---------------------------------------------------------------- + +// Torque won't normally add another string if it already exists with another casing, +// so this forces the addition. It should be called once near the start, such as in main.cs. +ConsoleFunction(addCaseSensitiveStrings,void,2,0,"[string1, string2, ...]" + "Adds case sensitive strings to the StringTable.") +{ + for(int i = 1; i < argc; i++) + StringTable->insert(argv[i], true); +} + //============================================================================= // Field Manipulators. //============================================================================= From 6a5fd4eceb5e14209cebb303a7d2eae790a3b748 Mon Sep 17 00:00:00 2001 From: Nathan Bowhay Date: Thu, 29 Jan 2015 11:53:25 -0800 Subject: [PATCH 006/109] date number to string added month and week number to string console functions. --- Engine/source/console/consoleFunctions.cpp | 43 ++++++++++++++++++++++ 1 file changed, 43 insertions(+) diff --git a/Engine/source/console/consoleFunctions.cpp b/Engine/source/console/consoleFunctions.cpp index f08ad2ac4..9fe5708fd 100644 --- a/Engine/source/console/consoleFunctions.cpp +++ b/Engine/source/console/consoleFunctions.cpp @@ -1233,6 +1233,49 @@ DefineConsoleFunction( getWordCount, S32, ( const char* text ),, //----------------------------------------------------------------------------- +DefineEngineFunction( monthNumToStr, String, ( S32 num, bool abbreviate ), (false), + "@brief returns month as a word given a number or \"\" if number is bad" + "@return month as a word given a number or \"\" if number is bad" + "@ingroup FileSystem") +{ + switch(num) + { + case 1: return abbreviate ? "Jan" : "January"; break; + case 2: return abbreviate ? "Feb" : "February"; break; + case 3: return abbreviate ? "Mar" : "March"; break; + case 4: return abbreviate ? "Apr" : "April"; break; + case 5: return "May"; break; + case 6: return abbreviate ? "Jun" : "June"; break; + case 7: return abbreviate ? "Jul" : "July"; break; + case 8: return abbreviate ? "Aug" : "August"; break; + case 9: return abbreviate ? "Sep" : "September"; break; + case 10: return abbreviate ? "Oct" : "October"; break; + case 11: return abbreviate ? "Nov" : "November"; break; + case 12: return abbreviate ? "Dec" : "December"; break; + default: return ""; + } +} + +DefineEngineFunction( weekdayNumToStr, String, ( S32 num, bool abbreviate ), (false), + "@brief returns weekday as a word given a number or \"\" if number is bad" + "@return weekday as a word given a number or \"\" if number is bad" + "@ingroup FileSystem") +{ + switch(num) + { + case 0: return abbreviate ? "Sun" : "Sunday"; break; + case 1: return abbreviate ? "Mon" : "Monday"; break; + case 2: return abbreviate ? "Tue" : "Tuesday"; break; + case 3: return abbreviate ? "Wed" : "Wednesday"; break; + case 4: return abbreviate ? "Thu" : "Thursday"; break; + case 5: return abbreviate ? "Fri" : "Friday"; break; + case 6: return abbreviate ? "Sat" : "Saturday"; break; + default: return ""; + } +} + +//----------------------------------------------------------------------------- + DefineConsoleFunction( getField, const char*, ( const char* text, S32 index ),, "Extract the field at the given @a index in the newline and/or tab separated list in @a text.\n" "Fields in @a text must be separated by newlines and/or tabs.\n" From 55b91606e6d6fce1805bf341bbc042025aba4dd6 Mon Sep 17 00:00:00 2001 From: Nathan Bowhay Date: Thu, 29 Jan 2015 11:54:44 -0800 Subject: [PATCH 007/109] Added more token functions Added a bunch more token functions and added comments to word equivalents to let you know about the token version. --- Engine/source/console/consoleFunctions.cpp | 113 +++++++++++++++++++++ 1 file changed, 113 insertions(+) diff --git a/Engine/source/console/consoleFunctions.cpp b/Engine/source/console/consoleFunctions.cpp index 9fe5708fd..f7680a82c 100644 --- a/Engine/source/console/consoleFunctions.cpp +++ b/Engine/source/console/consoleFunctions.cpp @@ -1141,6 +1141,7 @@ DefineConsoleFunction( getWord, const char*, ( const char* text, S32 index ),, "@endtsexample\n\n" "@see getWords\n" "@see getWordCount\n" + "@see getToken\n" "@see getField\n" "@see getRecord\n" "@ingroup FieldManip" ) @@ -1164,6 +1165,7 @@ DefineConsoleFunction( getWords, const char*, ( const char* text, S32 startIndex "@endtsexample\n\n" "@see getWord\n" "@see getWordCount\n" + "@see getTokens\n" "@see getFields\n" "@see getRecords\n" "@ingroup FieldManip" ) @@ -1188,6 +1190,7 @@ DefineConsoleFunction( setWord, const char*, ( const char* text, S32 index, cons "setWord( \"a b c d\", 2, \"f\" ) // Returns \"a b f d\"\n" "@endtsexample\n\n" "@see getWord\n" + "@see setToken\n" "@see setField\n" "@see setRecord\n" "@ingroup FieldManip" ) @@ -1207,6 +1210,7 @@ DefineConsoleFunction( removeWord, const char*, ( const char* text, S32 index ), "@tsexample\n" "removeWord( \"a b c d\", 2 ) // Returns \"a b d\"\n" "@endtsexample\n\n" + "@see removeToken\n" "@see removeField\n" "@see removeRecord\n" "@ingroup FieldManip" ) @@ -1224,6 +1228,7 @@ DefineConsoleFunction( getWordCount, S32, ( const char* text ),, "@tsexample\n" "getWordCount( \"a b c d e\" ) // Returns 5\n" "@endtsexample\n\n" + "@see getTokenCount\n" "@see getFieldCount\n" "@see getRecordCount\n" "@ingroup FieldManip" ) @@ -1597,6 +1602,114 @@ DefineConsoleFunction( nextToken, const char*, ( const char* str1, const char* t return ret; } +//----------------------------------------------------------------------------- + +DefineConsoleFunction( getToken, const char*, ( const char* text, const char* delimiters, S32 index ),, + "Extract the substring at the given @a index in the @a delimiters separated list in @a text.\n" + "@param text A @a delimiters list of substrings.\n" + "@param delimiters Character or characters that separate the list of substrings in @a text.\n" + "@param index The zero-based index of the substring to extract.\n" + "@return The substring at the given index or \"\" if the index is out of range.\n\n" + "@tsexample\n" + "getToken( \"a b c d\", \" \", 2 ) // Returns \"c\"\n" + "@endtsexample\n\n" + "@see getTokens\n" + "@see getTokenCount\n" + "@see getWord\n" + "@see getField\n" + "@see getRecord\n" + "@ingroup FieldManip" ) +{ + return Con::getReturnBuffer( StringUnit::getUnit(text, index, delimiters)); +} + +//----------------------------------------------------------------------------- + +DefineConsoleFunction( getTokens, const char*, ( const char* text, const char* delimiters, S32 startIndex, S32 endIndex ), ( -1 ), + "Extract a range of substrings separated by @a delimiters at the given @a startIndex onwards thru @a endIndex.\n" + "@param text A @a delimiters list of substrings.\n" + "@param delimiters Character or characters that separate the list of substrings in @a text.\n" + "@param startIndex The zero-based index of the first substring to extract from @a text.\n" + "@param endIndex The zero-based index of the last substring to extract from @a text. If this is -1, all words beginning " + "with @a startIndex are extracted from @a text.\n" + "@return A string containing the specified range of substrings from @a text or \"\" if @a startIndex " + "is out of range or greater than @a endIndex.\n\n" + "@tsexample\n" + "getTokens( \"a b c d\", \" \", 1, 2, ) // Returns \"b c\"\n" + "@endtsexample\n\n" + "@see getToken\n" + "@see getTokenCount\n" + "@see getWords\n" + "@see getFields\n" + "@see getRecords\n" + "@ingroup FieldManip" ) +{ + if( endIndex < 0 ) + endIndex = 1000000; + + return Con::getReturnBuffer( StringUnit::getUnits( text, startIndex, endIndex, delimiters ) ); +} + +//----------------------------------------------------------------------------- + +DefineConsoleFunction( setToken, const char*, ( const char* text, const char* delimiters, S32 index, const char* replacement ),, + "Replace the substring in @a text separated by @a delimiters at the given @a index with @a replacement.\n" + "@param text A @a delimiters list of substrings.\n" + "@param delimiters Character or characters that separate the list of substrings in @a text.\n" + "@param index The zero-based index of the substring to replace.\n" + "@param replacement The string with which to replace the substring.\n" + "@return A new string with the substring at the given @a index replaced by @a replacement or the original " + "string if @a index is out of range.\n\n" + "@tsexample\n" + "setToken( \"a b c d\", \" \", 2, \"f\" ) // Returns \"a b f d\"\n" + "@endtsexample\n\n" + "@see getToken\n" + "@see setWord\n" + "@see setField\n" + "@see setRecord\n" + "@ingroup FieldManip" ) +{ + return Con::getReturnBuffer( StringUnit::setUnit( text, index, replacement, delimiters) ); +} + +//----------------------------------------------------------------------------- + +DefineConsoleFunction( removeToken, const char*, ( const char* text, const char* delimiters, S32 index ),, + "Remove the substring in @a text separated by @a delimiters at the given @a index.\n" + "@param text A @a delimiters list of substrings.\n" + "@param delimiters Character or characters that separate the list of substrings in @a text.\n" + "@param index The zero-based index of the word in @a text.\n" + "@return A new string with the substring at the given index removed or the original string if @a index is " + "out of range.\n\n" + "@tsexample\n" + "removeToken( \"a b c d\", \" \", 2 ) // Returns \"a b d\"\n" + "@endtsexample\n\n" + "@see removeWord\n" + "@see removeField\n" + "@see removeRecord\n" + "@ingroup FieldManip" ) +{ + return Con::getReturnBuffer( StringUnit::removeUnit( text, index, delimiters ) ); +} + +//----------------------------------------------------------------------------- + +DefineConsoleFunction( getTokenCount, S32, ( const char* text, const char* delimiters),, + "Return the number of @a delimiters substrings in @a text.\n" + "@param text A @a delimiters list of substrings.\n" + "@param delimiters Character or characters that separate the list of substrings in @a text.\n" + "@return The number of @a delimiters substrings in @a text.\n\n" + "@tsexample\n" + "getTokenCount( \"a b c d e\", \" \" ) // Returns 5\n" + "@endtsexample\n\n" + "@see getWordCount\n" + "@see getFieldCount\n" + "@see getRecordCount\n" + "@ingroup FieldManip" ) +{ + return StringUnit::getUnitCount( text, delimiters ); +} + //============================================================================= // Tagged Strings. //============================================================================= From 5cfcb0cd4547a183f5f5924a3cc9c0f7dc19f8a4 Mon Sep 17 00:00:00 2001 From: Nathan Bowhay Date: Thu, 29 Jan 2015 11:59:06 -0800 Subject: [PATCH 008/109] fixed comment added path param documentation to display splash window. --- Engine/source/console/consoleFunctions.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/Engine/source/console/consoleFunctions.cpp b/Engine/source/console/consoleFunctions.cpp index f7680a82c..c15c6c944 100644 --- a/Engine/source/console/consoleFunctions.cpp +++ b/Engine/source/console/consoleFunctions.cpp @@ -2062,6 +2062,7 @@ DefineEngineFunction( gotoWebPage, void, ( const char* address ),, DefineEngineFunction( displaySplashWindow, bool, (const char* path), ("art/gui/splash.bmp"), "Display a startup splash window suitable for showing while the engine still starts up.\n\n" "@note This is currently only implemented on Windows.\n\n" + "@param path relative path to splash screen image to display.\n" "@return True if the splash window could be successfully initialized.\n\n" "@ingroup Platform" ) { From df2ca75b13d3edc9a3fa180ce0c96b30675005a9 Mon Sep 17 00:00:00 2001 From: Nathan Bowhay Date: Thu, 29 Jan 2015 12:02:40 -0800 Subject: [PATCH 009/109] get max dynamic verts in script you can now get the max dynamic vertices in script. --- Engine/source/console/consoleFunctions.cpp | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/Engine/source/console/consoleFunctions.cpp b/Engine/source/console/consoleFunctions.cpp index c15c6c944..1d5403818 100644 --- a/Engine/source/console/consoleFunctions.cpp +++ b/Engine/source/console/consoleFunctions.cpp @@ -37,6 +37,7 @@ #include "console/compiler.h" #include "platform/platformInput.h" #include "core/util/journal/journal.h" +#include "gfx/gfxEnums.h" #include "core/util/uuid.h" #ifdef TORQUE_DEMO_PURCHASE @@ -3065,3 +3066,10 @@ DefineEngineFunction( isToolBuild, bool, (),, return false; #endif } + +DefineEngineFunction( getMaxDynamicVerts, S32, (),, + "Get max number of allowable dynamic vertices in a single vertex buffer.\n\n" + "@return the max number of allowable dynamic vertices in a single vertex buffer" ) +{ + return MAX_DYNAMIC_VERTS / 2; +} \ No newline at end of file From c98e95e6ff9f0a22adee57544805701a81b71c39 Mon Sep 17 00:00:00 2001 From: Nathan Bowhay Date: Thu, 29 Jan 2015 15:09:41 -0800 Subject: [PATCH 010/109] Forgot supporting method Added string manipulation functions upload, requires these changes to compile. --- Engine/source/core/util/str.cpp | 100 ++++++++++++++++++++++++++++++++ Engine/source/core/util/str.h | 1 + 2 files changed, 101 insertions(+) diff --git a/Engine/source/core/util/str.cpp b/Engine/source/core/util/str.cpp index 3f4f3d8f9..59fd0ab46 100644 --- a/Engine/source/core/util/str.cpp +++ b/Engine/source/core/util/str.cpp @@ -1624,3 +1624,103 @@ String String::GetTrailingNumber(const char* str, S32& number) return base.substr(0, p - base.c_str()); } + +String String::GetFirstNumber(const char* str, U32& startPos, U32& endPos) +{ + // Check for trivial strings + if (!str || !str[0]) + return String::EmptyString; + + // Find the number at the end of the string + String base(str); + const char* p = base.c_str(); + const char* end = base.c_str() + base.length() - 1; + bool dec = false; + startPos = 0; + + //Check if we are just a digit + if(p == end && isdigit(*p)) + return base; + + //Look for the first digit + while ((p != end) && (dIsspace(*p) || !isdigit(*p))) + { + p++; + startPos++; + } + + //Handle if we are at the end and found nothing + if(p == end && !isdigit(*p)) + return ""; + + //update our end position at least to the start of our number + endPos = startPos; + + //Backup our ptr + const char* backup = p; + + //Check for any negative or decimal values + if(startPos > 0) + { + p--; + startPos--; + if(*p == '.') + { + dec = true; + + //ignore any duplicate periods + while ((p != base.c_str()) && (*p == '.')) + { + p--; + startPos--; + } + + //Found a decimal lets still check for negative sign + if(startPos > 0) + { + p--; + startPos--; + if((*p != '-') && (*p != '_')) + { + startPos++; + p++; + } + } + } + else if((*p != '-') && (*p != '_')) + { + //go back to where we where cause no decimal or negative sign found + startPos++; + p++; + } + } + + //Restore where we were + p = backup; + + //look for the end of the digits + bool justFoundDec = false; + while (p != end) + { + if(*p == '.') + { + if(dec && !justFoundDec) + break; + else + { + dec = true; + justFoundDec = true; + } + } + else if(!isdigit(*p)) + break; + else if(justFoundDec) + justFoundDec = false; + + p++; + endPos++; + } + + U32 len = (!isdigit(*p)) ? endPos - startPos : (endPos + 1) - startPos; + return base.substr(startPos, len); +} diff --git a/Engine/source/core/util/str.h b/Engine/source/core/util/str.h index 009484451..33fdb819d 100644 --- a/Engine/source/core/util/str.h +++ b/Engine/source/core/util/str.h @@ -190,6 +190,7 @@ public: static String ToUpper(const String &string); static String GetTrailingNumber(const char* str, S32& number); + static String GetFirstNumber(const char* str, U32& startPos, U32& endPos); /// @} From e03c3bb34fb7a2bdac847a23f10277285d9abf61 Mon Sep 17 00:00:00 2001 From: Ben Payne Date: Mon, 2 Feb 2015 18:17:37 -0500 Subject: [PATCH 011/109] Fix TORQUE_UNUSED for recent versions of MSVC Since there's now apparently no way to suppress the warning for a particular variable without adding at least some extra size to the executable, just turn the warning off in release builds. We leave it on in debug since it can sometimes help catch bugs, and we don't care about a little extra code in that configuration. --- Engine/source/platform/types.visualc.h | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/Engine/source/platform/types.visualc.h b/Engine/source/platform/types.visualc.h index e383c1ea7..36d075d07 100644 --- a/Engine/source/platform/types.visualc.h +++ b/Engine/source/platform/types.visualc.h @@ -32,6 +32,16 @@ typedef signed _int64 S64; typedef unsigned _int64 U64; +// The types.h version of TORQUE_UNUSED no longer works for recent versions of MSVC. +// Since it appears that MS has made this impossible to do in a zero-overhead way, +// just turn the warning off in release builds. +#undef TORQUE_UNUSED +#ifdef TORQUE_DEBUG +#define TORQUE_UNUSED(var) ((0,0) ? (void)(var) : (void)0) +#else +#pragma warning(disable: 4189) // local variable is initialized but not referenced +#define TORQUE_UNUSED(var) ((void)0) +#endif //-------------------------------------- // Compiler Version From c19a70814c270d51dd768b0ffdfa45ec9cd42f39 Mon Sep 17 00:00:00 2001 From: Ben Payne Date: Mon, 2 Feb 2015 18:24:01 -0500 Subject: [PATCH 012/109] Tidy up and fix the various Assert macros Rephrase the macros so that they can be used in expressions, and properly require semicolons. And add the semicolons where missing. --- Engine/source/T3D/fx/fxFoliageReplicator.cpp | 6 +-- Engine/source/T3D/fx/fxShapeReplicator.cpp | 2 +- Engine/source/console/codeBlock.cpp | 2 +- Engine/source/core/idGenerator.h | 2 +- Engine/source/environment/timeOfDay.cpp | 4 +- .../source/gfx/bitmap/loaders/bitmapTga.cpp | 2 +- Engine/source/gui/core/guiCanvas.cpp | 3 +- Engine/source/platform/platformAssert.h | 53 +++++++++---------- Engine/source/sim/netGhost.cpp | 4 +- Engine/source/util/catmullRom.cpp | 2 +- Engine/source/util/catmullRom.h | 4 +- 11 files changed, 40 insertions(+), 44 deletions(-) diff --git a/Engine/source/T3D/fx/fxFoliageReplicator.cpp b/Engine/source/T3D/fx/fxFoliageReplicator.cpp index 7e88b174f..ec17c2f18 100644 --- a/Engine/source/T3D/fx/fxFoliageReplicator.cpp +++ b/Engine/source/T3D/fx/fxFoliageReplicator.cpp @@ -426,7 +426,7 @@ void fxFoliageReplicator::CreateFoliage(void) Point3F MaxPoint( 0.5, 0.5, 0.5 ); // Check Host. - AssertFatal(isClientObject(), "Trying to create Foliage on Server, this is bad!") + AssertFatal(isClientObject(), "Trying to create Foliage on Server, this is bad!"); // Cannot continue without Foliage Texture! if (dStrlen(mFieldData.mFoliageFile) == 0) @@ -1134,7 +1134,7 @@ void fxFoliageReplicator::ProcessQuadrant(fxFoliageQuadrantNode* pParentNode, fx void fxFoliageReplicator::SyncFoliageReplicators(void) { // Check Host. - AssertFatal(isServerObject(), "We *MUST* be on server when Synchronising Foliage!") + AssertFatal(isServerObject(), "We *MUST* be on server when Synchronising Foliage!"); // Find the Replicator Set. SimSet *fxFoliageSet = dynamic_cast(Sim::findObject("fxFoliageSet")); @@ -1196,7 +1196,7 @@ void fxFoliageReplicator::DestroyFoliageItems() void fxFoliageReplicator::DestroyFoliage(void) { // Check Host. - AssertFatal(isClientObject(), "Trying to destroy Foliage on Server, this is bad!") + AssertFatal(isClientObject(), "Trying to destroy Foliage on Server, this is bad!"); // Destroy Quad-tree. mPotentialFoliageNodes = 0; diff --git a/Engine/source/T3D/fx/fxShapeReplicator.cpp b/Engine/source/T3D/fx/fxShapeReplicator.cpp index 908212716..6093fa0d5 100644 --- a/Engine/source/T3D/fx/fxShapeReplicator.cpp +++ b/Engine/source/T3D/fx/fxShapeReplicator.cpp @@ -224,7 +224,7 @@ void fxShapeReplicator::CreateShapes(void) } // Check Shapes. - AssertFatal(mCurrentShapeCount==0,"Shapes already present, this should not be possible!") + AssertFatal(mCurrentShapeCount==0,"Shapes already present, this should not be possible!"); // Check that we have a shape... if (!mFieldData.mShapeFile) return; diff --git a/Engine/source/console/codeBlock.cpp b/Engine/source/console/codeBlock.cpp index 05a2ab798..9b87a5ed3 100644 --- a/Engine/source/console/codeBlock.cpp +++ b/Engine/source/console/codeBlock.cpp @@ -61,7 +61,7 @@ CodeBlock::CodeBlock() CodeBlock::~CodeBlock() { // Make sure we aren't lingering in the current code block... - AssertFatal(smCurrentCodeBlock != this, "CodeBlock::~CodeBlock - Caught lingering in smCurrentCodeBlock!") + AssertFatal(smCurrentCodeBlock != this, "CodeBlock::~CodeBlock - Caught lingering in smCurrentCodeBlock!"); if(name) removeFromCodeList(); diff --git a/Engine/source/core/idGenerator.h b/Engine/source/core/idGenerator.h index a0ddc74ed..8c3467673 100644 --- a/Engine/source/core/idGenerator.h +++ b/Engine/source/core/idGenerator.h @@ -74,7 +74,7 @@ public: void free(U32 id) { - AssertFatal(id >= mIdBlockBase, "IdGenerator::alloc: invalid id, id does not belong to this IdGenerator.") + AssertFatal(id >= mIdBlockBase, "IdGenerator::alloc: invalid id, id does not belong to this IdGenerator."); if(id == mNextId - 1) { mNextId--; diff --git a/Engine/source/environment/timeOfDay.cpp b/Engine/source/environment/timeOfDay.cpp index 49b069b64..a0ae2fc83 100644 --- a/Engine/source/environment/timeOfDay.cpp +++ b/Engine/source/environment/timeOfDay.cpp @@ -402,7 +402,7 @@ void TimeOfDay::_getSunColor( ColorF *outColor ) const //simple check if ( mColorTargets[0].elevation != 0.0f ) { - AssertFatal(0, "TimeOfDay::GetColor() - First elevation must be 0.0 radians") + AssertFatal(0, "TimeOfDay::GetColor() - First elevation must be 0.0 radians"); outColor->set(1.0f, 1.0f, 1.0f); //mBandMod = 1.0f; //mCurrentBandColor = color; @@ -411,7 +411,7 @@ void TimeOfDay::_getSunColor( ColorF *outColor ) const if ( mColorTargets[mColorTargets.size()-1].elevation != M_PI_F ) { - AssertFatal(0, "Celestails::GetColor() - Last elevation must be PI") + AssertFatal(0, "Celestails::GetColor() - Last elevation must be PI"); outColor->set(1.0f, 1.0f, 1.0f); //mBandMod = 1.0f; //mCurrentBandColor = color; diff --git a/Engine/source/gfx/bitmap/loaders/bitmapTga.cpp b/Engine/source/gfx/bitmap/loaders/bitmapTga.cpp index 15812ff9b..dff46bf03 100644 --- a/Engine/source/gfx/bitmap/loaders/bitmapTga.cpp +++ b/Engine/source/gfx/bitmap/loaders/bitmapTga.cpp @@ -483,7 +483,7 @@ static bool sReadTGA(Stream &stream, GBitmap *bitmap) static bool sWriteTGA(GBitmap *bitmap, Stream &stream, U32 compressionLevel) { - AssertISV(false, "GBitmap::writeTGA - doesn't support writing tga files!") + AssertISV(false, "GBitmap::writeTGA - doesn't support writing tga files!"); return false; } diff --git a/Engine/source/gui/core/guiCanvas.cpp b/Engine/source/gui/core/guiCanvas.cpp index 4dba87310..6c68e410c 100644 --- a/Engine/source/gui/core/guiCanvas.cpp +++ b/Engine/source/gui/core/guiCanvas.cpp @@ -712,7 +712,8 @@ bool GuiCanvas::processMouseEvent(InputEventInfo &inputEvent) // Need to query platform for specific things AssertISV(mPlatformWindow, "GuiCanvas::processMouseEvent - no window present!"); PlatformCursorController *pController = mPlatformWindow->getCursorController(); - AssertFatal(pController != NULL, "GuiCanvas::processInputEvent - No Platform Controller Found") + AssertFatal(pController != NULL, "GuiCanvas::processInputEvent - No Platform Controller Found"); + //copy the modifier into the new event mLastEvent.modifier = inputEvent.modifier; diff --git a/Engine/source/platform/platformAssert.h b/Engine/source/platform/platformAssert.h index c26f255c0..86ccb1b23 100644 --- a/Engine/source/platform/platformAssert.h +++ b/Engine/source/platform/platformAssert.h @@ -64,19 +64,17 @@ public: #ifdef TORQUE_ENABLE_ASSERTS - /*! - Assert that the statement x is true, and continue processing. +/*! + Assert that the statement x is true, and continue processing. - If the statment x is true, continue processing. + If the statment x is true, continue processing. - If the statement x is false, log the file and line where the assert occured, - the message y and continue processing. + If the statement x is false, log the file and line where the assert occured, + the message y and continue processing. - These asserts are only present in DEBUG builds. - */ - #define AssertWarn(x, y) \ - { if ((x)==0) \ - ::PlatformAssert::processAssert(::PlatformAssert::Warning, __FILE__, __LINE__, y); } + These asserts are only present in DEBUG builds. + */ +#define AssertWarn(x, y) (void)(!!(x) || ::PlatformAssert::processAssert(::PlatformAssert::Warning, __FILE__, __LINE__, y)) /*! Helper macro called when AssertFatal failed. @@ -86,27 +84,27 @@ public: #define ON_FAIL_ASSERTFATAL #endif - /*! - Assert that the statement x is true, otherwise halt. +/*! + Assert that the statement x is true, otherwise halt. - If the statement x is true, continue processing. + If the statement x is true, continue processing. - If the statement x is false, log the file and line where the assert occured, - the message y and displaying a dialog containing the message y. The user then - has the option to halt or continue causing the debugger to break. + If the statement x is false, log the file and line where the assert occured, + the message y and displaying a dialog containing the message y. The user then + has the option to halt or continue causing the debugger to break. - These asserts are only present in DEBUG builds. + These asserts are only present in DEBUG builds. - This assert is very useful for verifying data as well as function entry and - exit conditions. - */ - #define AssertFatal(x, y) \ - { if (((bool)(x))==false) \ - { if ( ::PlatformAssert::processAssert(::PlatformAssert::Fatal, __FILE__, __LINE__, y) ) { ::Platform::debugBreak(); } } } + This assert is very useful for verifying data as well as function entry and + exit conditions. + */ +#define AssertFatal(x, y) ((!(x) && ::PlatformAssert::processAssert(::PlatformAssert::Fatal, __FILE__, __LINE__, y)) ? ::Platform::debugBreak() : (void)0) \ #else - #define AssertFatal(x, y) { TORQUE_UNUSED(x); TORQUE_UNUSED(y); } - #define AssertWarn(x, y) { TORQUE_UNUSED(x); TORQUE_UNUSED(y); } + +#define AssertFatal(x, y) TORQUE_UNUSED(x) +#define AssertWarn(x, y) TORQUE_UNUSED(x) + #endif /*! @@ -121,10 +119,7 @@ public: This assert should only be used for rare conditions where the application cannot continue execution without seg-faulting and you want to display a nice exit message. */ -#define AssertISV(x, y) \ - { if ((x)==0) \ -{ if ( ::PlatformAssert::processAssert(::PlatformAssert::Fatal_ISV, __FILE__, __LINE__, y) ) { ::Platform::debugBreak(); } } } - +#define AssertISV(x, y) ((!(x) && ::PlatformAssert::processAssert(::PlatformAssert::Fatal_ISV, __FILE__, __LINE__, y)) ? ::Platform::debugBreak() : (void)0) \ /*! Sprintf style string formating into a fixed temporary buffer. diff --git a/Engine/source/sim/netGhost.cpp b/Engine/source/sim/netGhost.cpp index 13acfc7e3..9ea42fc06 100644 --- a/Engine/source/sim/netGhost.cpp +++ b/Engine/source/sim/netGhost.cpp @@ -958,7 +958,7 @@ void NetConnection::activateGhosting() // Iterate through the scope always objects... for (j = mGhostZeroUpdateIndex - 1; j >= 0; j--) { - AssertFatal((mGhostArray[j]->flags & GhostInfo::ScopeAlways) != 0, "NetConnection::activateGhosting: Non-scope always in the scope always list.") + AssertFatal((mGhostArray[j]->flags & GhostInfo::ScopeAlways) != 0, "NetConnection::activateGhosting: Non-scope always in the scope always list."); // Clear the ghost update mask and flags appropriately. mGhostArray[j]->updateMask = 0; @@ -1009,7 +1009,7 @@ void NetConnection::activateGhosting() // Iterate through the scope always objects... for (j = mGhostZeroUpdateIndex - 1; j >= 0; j--) { - AssertFatal((mGhostArray[j]->flags & GhostInfo::ScopeAlways) != 0, "NetConnection::activateGhosting: Non-scope always in the scope always list.") + AssertFatal((mGhostArray[j]->flags & GhostInfo::ScopeAlways) != 0, "NetConnection::activateGhosting: Non-scope always in the scope always list."); // Clear the ghost update mask and flags appropriately. mGhostArray[j]->updateMask = 0; diff --git a/Engine/source/util/catmullRom.cpp b/Engine/source/util/catmullRom.cpp index 10452c18c..7ac676a15 100644 --- a/Engine/source/util/catmullRom.cpp +++ b/Engine/source/util/catmullRom.cpp @@ -48,7 +48,7 @@ CatmullRomBase::CatmullRomBase() void CatmullRomBase::_initialize( U32 count, const F32 *times ) { //AssertFatal( times, "CatmullRomBase::_initialize() - Got null position!" ) - AssertFatal( count > 1, "CatmullRomBase::_initialize() - Must have more than 2 points!" ) + AssertFatal( count > 1, "CatmullRomBase::_initialize() - Must have more than 2 points!" ); // set up arrays mTimes = new F32[count]; diff --git a/Engine/source/util/catmullRom.h b/Engine/source/util/catmullRom.h index f4c9decb0..84bb272aa 100644 --- a/Engine/source/util/catmullRom.h +++ b/Engine/source/util/catmullRom.h @@ -142,8 +142,8 @@ inline void CatmullRom::clear() template inline void CatmullRom::initialize( U32 count, const TYPE *positions, const F32 *times ) { - AssertFatal( positions, "CatmullRom::initialize - Got null position!" ) - AssertFatal( count > 1, "CatmullRom::initialize - Must have more than 2 points!" ) + AssertFatal( positions, "CatmullRom::initialize - Got null position!" ); + AssertFatal( count > 1, "CatmullRom::initialize - Must have more than 2 points!" ); // Clean up any previous state. clear(); From 222be2bb723f6536bf33ce139e352dfc52c45e6f Mon Sep 17 00:00:00 2001 From: Ben Payne Date: Mon, 2 Feb 2015 19:11:20 -0500 Subject: [PATCH 013/109] Remove dead function --- Engine/source/platform/platformAssert.cpp | 18 ------------------ 1 file changed, 18 deletions(-) diff --git a/Engine/source/platform/platformAssert.cpp b/Engine/source/platform/platformAssert.cpp index 7e1d3b93a..f1a2b2957 100644 --- a/Engine/source/platform/platformAssert.cpp +++ b/Engine/source/platform/platformAssert.cpp @@ -69,24 +69,6 @@ bool PlatformAssert::displayMessageBox(const char *title, const char *message, b } static const char *typeName[] = { "Unknown", "Fatal-ISV", "Fatal", "Warning" }; -//------------------------------------------------------------------------------ -static bool askToEnterDebugger(const char* message ) -{ - static bool haveAsked = false; - static bool useDebugger = true; - if(!haveAsked ) - { - static char tempBuff[1024]; - dSprintf( tempBuff, 1024, "Torque has encountered an assertion with message\n\n" - "%s\n\n" - "Would you like to use the debugger? If you cancel, you won't be asked" - " again until you restart Torque.", message); - - useDebugger = Platform::AlertOKCancel("Use debugger?", tempBuff ); - haveAsked = true; - } - return useDebugger; -} //-------------------------------------- From 2efe1a9c0a0b420a10742235c7bdcfe7b599cbc1 Mon Sep 17 00:00:00 2001 From: Nathan Bowhay Date: Tue, 3 Feb 2015 10:59:32 -0800 Subject: [PATCH 014/109] Added More Vector math functions Added VectorMul (vector multiply), VectorDiv (vector divide, and VectorMidPoint to find the midpoint of two vectors. --- Engine/source/math/mathTypes.cpp | 88 ++++++++++++++++++++++++++++++++ 1 file changed, 88 insertions(+) diff --git a/Engine/source/math/mathTypes.cpp b/Engine/source/math/mathTypes.cpp index ce0f198d1..807e3bc60 100644 --- a/Engine/source/math/mathTypes.cpp +++ b/Engine/source/math/mathTypes.cpp @@ -659,6 +659,66 @@ DefineConsoleFunction( VectorScale, VectorF, ( VectorF a, F32 scalar ),, { return a * scalar; } +DefineConsoleFunction( VectorMul, VectorF, ( VectorF a, VectorF b ),, + "Multiplies two vectors.\n" + "@param a The first vector.\n" + "@param b The second vector.\n" + "@return The vector @a a * @a b.\n\n" + "@tsexample\n" + "//-----------------------------------------------------------------------------\n" + "//\n" + "// VectorMul( %a, %b );\n" + "//\n" + "// The multiplication of vector a, (ax, ay, az), and vector b, (bx, by, bz) is:\n" + "//\n" + "// a * b = ( ax * bx, ay * by, az * bz )\n" + "//\n" + "//-----------------------------------------------------------------------------\n\n" + + "%a = \"1 0 0\";\n" + "%b = \"0 1 0\";\n\n" + + "// %r = \"( 1 * 0, 0 * 1, 0 * 0 )\";\n" + "// %r = \"0 0 0\";\n" + "%r = VectorMul( %a, %b );\n" + "@endtsexample\n\n" + "@ingroup Vectors" ) +{ + return a * b; +} + +DefineConsoleFunction( VectorDiv, VectorF, ( VectorF a, VectorF b ),, + "Divide two vectors.\n" + "@param a The first vector.\n" + "@param b The second vector.\n" + "@return The vector @a a / @a b.\n\n" + "@tsexample\n" + "//-----------------------------------------------------------------------------\n" + "//\n" + "// VectorDiv( %a, %b );\n" + "//\n" + "// The division of vector a, (ax, ay, az), and vector b, (bx, by, bz) is:\n" + "//\n" + "// a * b = ( ax / bx, ay / by, az / bz )\n" + "//\n" + "//-----------------------------------------------------------------------------\n\n" + + "%a = \"1 1 1\";\n" + "%b = \"2 2 2\";\n\n" + + "// %r = \"( 1 / 2, 1 / 2, 1 / 2 )\";\n" + "// %r = \"0.5 0.5 0.5\";\n" + "%r = VectorDiv( %a, %b );\n" + "@endtsexample\n\n" + "@ingroup Vectors" ) +{ + //this is kind of bad, but so is dividing by 0 + if(b.x == 0) b.x = 0.000001f; + if(b.y == 0) b.y = 0.000001f; + if(b.z == 0) b.z = 0.000001f; + + return a / b; +} //----------------------------------------------------------------------------- @@ -789,6 +849,34 @@ DefineConsoleFunction( VectorDist, F32, ( VectorF a, VectorF b ),, //----------------------------------------------------------------------------- +DefineConsoleFunction( VectorMidPoint, VectorF, ( VectorF a, VectorF b ),, + "Gets the midpoint between the two vectors.\n" + "@param a The first vector.\n" + "@param b The second vector.\n" + "@return The vector (@a a + @a b) / 2.\n\n" + "@tsexample\n" + "//-----------------------------------------------------------------------------\n" + "//\n" + "// VectorMidPoint( %a, %b );\n" + "//\n" + "// The midpoint of vector a, (ax, ay, az), and vector b, (bx, by, bz) is:\n" + "//\n" + "// (a + b)/2 = ( (ax + bx) /2, ay + by) /2, (az + bz) /2 )\n" + "//\n" + "//-----------------------------------------------------------------------------\n" +// "%a = \"1 0 0\";\n" +// "%b = \"0 1 0\";\n\n" +// "// %r = \"( 1 + 0, 0 + 1, 0 + 0 )\";\n" +// "// %r = \"1 1 0\";\n" +// "%r = VectorAdd( %a, %b );\n" + "@endtsexample\n\n" + "@ingroup Vectors") +{ + return (a + b)/2.0f; +} + +//----------------------------------------------------------------------------- + DefineConsoleFunction( VectorLen, F32, ( VectorF v ),, "Calculate the magnitude of the given vector.\n" "@param v A vector.\n" From 686b9fced931833fc30e043472e92a6709cdccbf Mon Sep 17 00:00:00 2001 From: Nathan Bowhay Date: Tue, 3 Feb 2015 11:52:06 -0800 Subject: [PATCH 015/109] Round function can now round to number of digits Changed round function to support optional n parameter to round to that many digits. --- Engine/source/math/mConsoleFunctions.cpp | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/Engine/source/math/mConsoleFunctions.cpp b/Engine/source/math/mConsoleFunctions.cpp index 1a11fe23e..f9b352c28 100644 --- a/Engine/source/math/mConsoleFunctions.cpp +++ b/Engine/source/math/mConsoleFunctions.cpp @@ -94,13 +94,18 @@ DefineConsoleFunction( mFloor, S32, ( F32 v ),, return (S32)mFloor( v ); } -DefineConsoleFunction( mRound, S32, ( F32 v ),, - "Round v to the nth decimal place or the nearest whole number by default." - "@param v Value to roundn" - "@return The rounded value as a S32." - "@ingroup Math" ) + +DefineConsoleFunction( mRound, F32, ( F32 v, S32 n ), (0), + "Round v to the nth decimal place or the nearest whole number by default." + "@param v Value to round\n" + "@param n Number of decimal places to round to, 0 by default\n" + "@return The rounded value as a S32." + "@ingroup Math" ) { - return mRound(v); + if(n <= 0) + return mRound(v); + else + return mRound(v, n); } DefineConsoleFunction( mCeil, S32, ( F32 v ),, From 40999be7c1952af154609e0ecbc9933e13c5e391 Mon Sep 17 00:00:00 2001 From: Nathan Bowhay Date: Thu, 5 Feb 2015 11:19:41 -0800 Subject: [PATCH 016/109] Improved file open dialogue Added some extra parameters to open file dialogue and added a check to findConstructor so it doesn't throw errors. --- .../Empty/game/tools/gui/openFileDialog.ed.cs | 25 +++++++++++++------ .../Full/game/tools/gui/openFileDialog.ed.cs | 25 +++++++++++++------ 2 files changed, 36 insertions(+), 14 deletions(-) diff --git a/Templates/Empty/game/tools/gui/openFileDialog.ed.cs b/Templates/Empty/game/tools/gui/openFileDialog.ed.cs index b988d3361..50c7cdfcc 100644 --- a/Templates/Empty/game/tools/gui/openFileDialog.ed.cs +++ b/Templates/Empty/game/tools/gui/openFileDialog.ed.cs @@ -20,27 +20,38 @@ // IN THE SOFTWARE. //----------------------------------------------------------------------------- -function getLoadFilename(%filespec, %callback, %currentFile) -{ +function getLoadFilename(%filespec, %callback, %currentFile, %getRelative, %defaultPath) +{ + //If no default path passed in then try to get one from the file + if(%defaultPath $= "") + { + if ( filePath( %currentFile ) !$= "" ) + %defaultPath = filePath(%currentFile); + } + %dlg = new OpenFileDialog() { Filters = %filespec; DefaultFile = %currentFile; + DefaultPath = %defaultPath; ChangePath = false; MustExist = true; MultipleFiles = false; }; - if ( filePath( %currentFile ) !$= "" ) - %dlg.DefaultPath = filePath(%currentFile); - - if ( %dlg.Execute() ) + %ok = %dlg.Execute(); + if ( %ok ) { - eval(%callback @ "(\"" @ %dlg.FileName @ "\");"); + %file = %dlg.FileName; + if(%getRelative) + %file = strreplace(%file,getWorkingDirectory() @ "/", ""); + eval(%callback @ "(\"" @ %file @ "\");"); $Tools::FileDialogs::LastFilePath = filePath( %dlg.FileName ); } %dlg.delete(); + + return %ok; } // Opens a choose file dialog with format filters already loaded diff --git a/Templates/Full/game/tools/gui/openFileDialog.ed.cs b/Templates/Full/game/tools/gui/openFileDialog.ed.cs index b988d3361..50c7cdfcc 100644 --- a/Templates/Full/game/tools/gui/openFileDialog.ed.cs +++ b/Templates/Full/game/tools/gui/openFileDialog.ed.cs @@ -20,27 +20,38 @@ // IN THE SOFTWARE. //----------------------------------------------------------------------------- -function getLoadFilename(%filespec, %callback, %currentFile) -{ +function getLoadFilename(%filespec, %callback, %currentFile, %getRelative, %defaultPath) +{ + //If no default path passed in then try to get one from the file + if(%defaultPath $= "") + { + if ( filePath( %currentFile ) !$= "" ) + %defaultPath = filePath(%currentFile); + } + %dlg = new OpenFileDialog() { Filters = %filespec; DefaultFile = %currentFile; + DefaultPath = %defaultPath; ChangePath = false; MustExist = true; MultipleFiles = false; }; - if ( filePath( %currentFile ) !$= "" ) - %dlg.DefaultPath = filePath(%currentFile); - - if ( %dlg.Execute() ) + %ok = %dlg.Execute(); + if ( %ok ) { - eval(%callback @ "(\"" @ %dlg.FileName @ "\");"); + %file = %dlg.FileName; + if(%getRelative) + %file = strreplace(%file,getWorkingDirectory() @ "/", ""); + eval(%callback @ "(\"" @ %file @ "\");"); $Tools::FileDialogs::LastFilePath = filePath( %dlg.FileName ); } %dlg.delete(); + + return %ok; } // Opens a choose file dialog with format filters already loaded From 7ef3a6495784c19824fbb0e414a54dd2ba54f761 Mon Sep 17 00:00:00 2001 From: Nathan Bowhay Date: Thu, 5 Feb 2015 11:28:19 -0800 Subject: [PATCH 017/109] Added getLocalTime console function Add getLocalTime console function so that script has access to the local time. --- Engine/source/app/game.cpp | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/Engine/source/app/game.cpp b/Engine/source/app/game.cpp index 4579b4819..2f0e30c77 100644 --- a/Engine/source/app/game.cpp +++ b/Engine/source/app/game.cpp @@ -202,6 +202,26 @@ DefineConsoleFunction( getRealTime, S32, (), , "()" return Platform::getRealMilliseconds(); } +ConsoleFunction( getLocalTime, const char *, 1, 1, "Return the current local time as: weekday month day year hour min sec.\n\n" + "Local time is platform defined.") +{ + Platform::LocalTime lt; + Platform::getLocalTime(lt); + + static const U32 bufSize = 128; + char *retBuffer = Con::getReturnBuffer(bufSize); + dSprintf(retBuffer, bufSize, "%d %d %d %d %02d %02d %02d", + lt.weekday, + lt.month + 1, + lt.monthday, + lt.year + 1900, + lt.hour, + lt.min, + lt.sec); + + return retBuffer; +} + ConsoleFunctionGroupEnd(Platform); //----------------------------------------------------------------------------- From a0250e6c6bd3b9c88858c0fc35a3f5da6a5807df Mon Sep 17 00:00:00 2001 From: Nathan Bowhay Date: Thu, 5 Feb 2015 11:41:44 -0800 Subject: [PATCH 018/109] Added callback when we read all lines Added a script callback when all lines have been read. --- Engine/source/app/net/tcpObject.cpp | 6 ++++++ Engine/source/app/net/tcpObject.h | 1 + 2 files changed, 7 insertions(+) diff --git a/Engine/source/app/net/tcpObject.cpp b/Engine/source/app/net/tcpObject.cpp index f2818b426..6d1ccaa49 100644 --- a/Engine/source/app/net/tcpObject.cpp +++ b/Engine/source/app/net/tcpObject.cpp @@ -138,6 +138,10 @@ IMPLEMENT_CALLBACK(TCPObject, onLine, void, (const char* line), (line), "@param line Data sent from the server.\n" ); +IMPLEMENT_CALLBACK(TCPObject, onEndReceive, void, (), (), + "@brief Called when we are done reading all lines.\n\n" + ); + IMPLEMENT_CALLBACK(TCPObject, onDNSResolved, void, (),(), "Called whenever the DNS has been resolved.\n" ); @@ -499,6 +503,8 @@ void processConnectedReceiveEvent(NetSocket sock, RawData incomingData) size -= ret; buffer += ret; } + + tcpo->onEndReceive_callback(); } void processConnectedAcceptEvent(NetSocket listeningPort, NetSocket newConnection, NetAddress originatingAddress) diff --git a/Engine/source/app/net/tcpObject.h b/Engine/source/app/net/tcpObject.h index 9c4582eab..9d14868ad 100644 --- a/Engine/source/app/net/tcpObject.h +++ b/Engine/source/app/net/tcpObject.h @@ -36,6 +36,7 @@ public: DECLARE_CALLBACK(void, onConnectionRequest, (const char* address, const char* ID)); DECLARE_CALLBACK(void, onLine, (const char* line)); + DECLARE_CALLBACK(void, onEndReceive, ()); DECLARE_CALLBACK(void, onDNSResolved,()); DECLARE_CALLBACK(void, onDNSFailed, ()); DECLARE_CALLBACK(void, onConnected, ()); From 5f0b3984fc0137382560f74dffdec90468b2c290 Mon Sep 17 00:00:00 2001 From: Nathan Bowhay Date: Thu, 5 Feb 2015 11:43:56 -0800 Subject: [PATCH 019/109] Added sendFile method Added a method to send an entire file over tcp. --- Engine/source/app/net/tcpObject.cpp | 33 +++++++++++++++++++++++++++++ Engine/source/app/net/tcpObject.h | 6 ++++++ 2 files changed, 39 insertions(+) diff --git a/Engine/source/app/net/tcpObject.cpp b/Engine/source/app/net/tcpObject.cpp index 6d1ccaa49..b156bfa04 100644 --- a/Engine/source/app/net/tcpObject.cpp +++ b/Engine/source/app/net/tcpObject.cpp @@ -27,6 +27,7 @@ #include "console/consoleInternal.h" #include "core/strings/stringUnit.h" #include "console/engineAPI.h" +#include "core/stream/fileStream.h" TCPObject *TCPObject::table[TCPObject::TableSize] = {0, }; @@ -404,6 +405,29 @@ void TCPObject::send(const U8 *buffer, U32 len) Net::sendtoSocket(mTag, buffer, S32(len)); } +bool TCPObject::sendFile(const char* fileName) +{ + //Open the file for reading + FileStream readFile; + if(!readFile.open(fileName, Torque::FS::File::Read)) + { + return false; + } + + //Read each byte into our buffer + Vector buffer(readFile.getStreamSize()); + U8 byte; + while(readFile.read(&byte)) + { + buffer.push_back(byte); + } + + //Send the buffer + send(buffer.address(), buffer.size()); + + return true; +} + DefineEngineMethod(TCPObject, send, void, (const char *data),, "@brief Transmits the data string to the connected computer.\n\n" @@ -425,6 +449,15 @@ DefineEngineMethod(TCPObject, send, void, (const char *data),, object->send( (const U8*)data, dStrlen(data) ); } +DefineEngineMethod(TCPObject, sendFile, bool, (const char *fileName),, + "@brief Transmits the file in binary to the connected computer.\n\n" + + "@param fileName The filename of the file to transfer.\n") +{ + return object->sendFile(fileName); +} + + DefineEngineMethod(TCPObject, listen, void, (U32 port),, "@brief Start listening on the specified port for connections.\n\n" diff --git a/Engine/source/app/net/tcpObject.h b/Engine/source/app/net/tcpObject.h index 9d14868ad..39b9464ba 100644 --- a/Engine/source/app/net/tcpObject.h +++ b/Engine/source/app/net/tcpObject.h @@ -82,6 +82,12 @@ public: bool processArguments(S32 argc, ConsoleValueRef *argv); void send(const U8 *buffer, U32 bufferLen); + + ///Send an entire file over tcp + ///@arg fileName Full path to file you want to send + ///@return true if file was sent, false if not (file doesn't exist) + bool sendFile(const char* fileName); + void addToTable(NetSocket newTag); void removeFromTable(); From 0a768090928d07912c14af10cde2bdc86d323f51 Mon Sep 17 00:00:00 2001 From: Nathan Bowhay Date: Thu, 5 Feb 2015 11:44:32 -0800 Subject: [PATCH 020/109] Opened finishLastLine to script Opened finishLastLine to script so you can eat the rest of the lines in script. --- Engine/source/app/net/tcpObject.cpp | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/Engine/source/app/net/tcpObject.cpp b/Engine/source/app/net/tcpObject.cpp index b156bfa04..28e91ba45 100644 --- a/Engine/source/app/net/tcpObject.cpp +++ b/Engine/source/app/net/tcpObject.cpp @@ -457,6 +457,11 @@ DefineEngineMethod(TCPObject, sendFile, bool, (const char *fileName),, return object->sendFile(fileName); } +DefineEngineMethod(TCPObject, finishLastLine, void, (),, + "@brief Eat the rest of the lines.\n") +{ + object->finishLastLine(); +} DefineEngineMethod(TCPObject, listen, void, (U32 port),, "@brief Start listening on the specified port for connections.\n\n" From 02f859c150d69e6339a59bd41a6035115a66cdb7 Mon Sep 17 00:00:00 2001 From: Nathan Bowhay Date: Wed, 11 Feb 2015 10:53:34 -0800 Subject: [PATCH 021/109] Fixed spacing to fit GG standards. Fixed tabs to 3 spaces. --- Engine/source/console/consoleFunctions.cpp | 364 ++++++++++----------- Engine/source/core/util/str.cpp | 166 +++++----- Engine/source/math/mConsoleFunctions.cpp | 18 +- 3 files changed, 274 insertions(+), 274 deletions(-) diff --git a/Engine/source/console/consoleFunctions.cpp b/Engine/source/console/consoleFunctions.cpp index 1d5403818..4c12f2550 100644 --- a/Engine/source/console/consoleFunctions.cpp +++ b/Engine/source/console/consoleFunctions.cpp @@ -53,129 +53,129 @@ static char scriptFilenameBuffer[1024]; bool isInt(const char* str) { - int len = dStrlen(str); - if(len <= 0) - return false; + int len = dStrlen(str); + if(len <= 0) + return false; - // Ingore whitespace - int start = 0; - for(int i = start; i < len; i++) - if(str[i] != ' ') - { - start = i; - break; - } + // Ignore whitespace + int start = 0; + for(int i = start; i < len; i++) + if(str[i] != ' ') + { + start = i; + break; + } - for(int i = start; i < len; i++) - switch(str[i]) - { - case '+': case '-': - if(i != 0) - return false; - break; - case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9': case '0': - break; - case ' ': // ignore whitespace - for(int j = i+1; j < len; j++) - if(str[j] != ' ') - return false; - return true; - break; - default: - return false; - } - return true; + for(int i = start; i < len; i++) + switch(str[i]) + { + case '+': case '-': + if(i != 0) + return false; + break; + case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9': case '0': + break; + case ' ': // ignore whitespace + for(int j = i+1; j < len; j++) + if(str[j] != ' ') + return false; + return true; + break; + default: + return false; + } + return true; } bool isFloat(const char* str, bool sciOk = false) { - int len = dStrlen(str); - if(len <= 0) - return false; + int len = dStrlen(str); + if(len <= 0) + return false; - // Ingore whitespace - int start = 0; - for(int i = start; i < len; i++) - if(str[i] != ' ') - { - start = i; - break; - } + // Ingore whitespace + int start = 0; + for(int i = start; i < len; i++) + if(str[i] != ' ') + { + start = i; + break; + } - bool seenDot = false; - int eLoc = -1; - for(int i = 0; i < len; i++) - switch(str[i]) - { - case '+': case '-': - if(sciOk) - { - //Haven't found e or scientific notation symbol - if(eLoc == -1) - { - //only allowed in beginning - if(i != 0) - return false; - } - else - { - //if not right after the e - if(i != (eLoc + 1)) - return false; - } - } - else - { - //only allowed in beginning - if(i != 0) - return false; - } - break; - case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9': case '0': - break; - case 'e': case 'E': - if(!sciOk) - return false; - else - { - //already saw it so can't have 2 - if(eLoc != -1) - return false; + bool seenDot = false; + int eLoc = -1; + for(int i = 0; i < len; i++) + switch(str[i]) + { + case '+': case '-': + if(sciOk) + { + //Haven't found e or scientific notation symbol + if(eLoc == -1) + { + //only allowed in beginning + if(i != 0) + return false; + } + else + { + //if not right after the e + if(i != (eLoc + 1)) + return false; + } + } + else + { + //only allowed in beginning + if(i != 0) + return false; + } + break; + case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9': case '0': + break; + case 'e': case 'E': + if(!sciOk) + return false; + else + { + //already saw it so can't have 2 + if(eLoc != -1) + return false; - eLoc = i; - } - break; - case '.': - if(seenDot | (sciOk && eLoc != -1)) - return false; - seenDot = true; - break; - case ' ': // ignore whitespace - for(int j = i+1; j < len; j++) - if(str[j] != ' ') - return false; - return true; - break; - default: - return false; - } - return true; + eLoc = i; + } + break; + case '.': + if(seenDot | (sciOk && eLoc != -1)) + return false; + seenDot = true; + break; + case ' ': // ignore whitespace + for(int j = i+1; j < len; j++) + if(str[j] != ' ') + return false; + return true; + break; + default: + return false; + } + return true; } bool isValidIP(const char* ip) { - unsigned b1, b2, b3, b4; - unsigned char c; - int rc = dSscanf(ip, "%3u.%3u.%3u.%3u%c", &b1, &b2, &b3, &b4, &c); - if (rc != 4 && rc != 5) return false; - if ((b1 | b2 | b3 | b4) > 255) return false; - if (dStrspn(ip, "0123456789.") < dStrlen(ip)) return false; - return true; + unsigned b1, b2, b3, b4; + unsigned char c; + int rc = dSscanf(ip, "%3u.%3u.%3u.%3u%c", &b1, &b2, &b3, &b4, &c); + if (rc != 4 && rc != 5) return false; + if ((b1 | b2 | b3 | b4) > 255) return false; + if (dStrspn(ip, "0123456789.") < dStrlen(ip)) return false; + return true; } bool isValidPort(U16 port) { - return (port >= 0 && port <=65535); + return (port >= 0 && port <=65535); } //============================================================================= @@ -380,29 +380,29 @@ DefineConsoleFunction( strlenskip, S32, ( const char* str, const char* first, co "@return The length of the given string skipping blocks of text between characters.\n" "@ingroup Strings" ) { - const UTF8* pos = str; - U32 size = 0; - U32 length = dStrlen(str); - bool count = true; + const UTF8* pos = str; + U32 size = 0; + U32 length = dStrlen(str); + bool count = true; - //loop through each character counting each character, skipping tags (anything with < followed by >) - for(U32 i = 0; i < length; i++, pos++) - { - if(count) - { - if(*pos == first[0]) - count = false; - else - size++; - } - else - { - if(*pos == last[0]) - count = true; - } - } + //loop through each character counting each character, skipping tags (anything with < followed by >) + for(U32 i = 0; i < length; i++, pos++) + { + if(count) + { + if(*pos == first[0]) + count = false; + else + size++; + } + else + { + if(*pos == last[0]) + count = true; + } + } - return S32(size); + return S32(size); } //----------------------------------------------------------------------------- @@ -831,9 +831,9 @@ DefineConsoleFunction( getFirstNumber, String, ( const char* str ),, "@param str The string from which to read out the first number.\n" "@return String representation of the number or "" if no number.\n\n") { - U32 start; - U32 end; - return String::GetFirstNumber(str, start, end); + U32 start; + U32 end; + return String::GetFirstNumber(str, start, end); } //---------------------------------------------------------------- @@ -1031,17 +1031,17 @@ DefineConsoleFunction( strToggleCaseToWords, const char*, ( const char* str ),, "@endtsexample\n" "@ingroup Strings" ) { - String newStr; - for(S32 i = 0; str[i]; i++) - { - //If capitol add a space - if(i != 0 && str[i] >= 65 && str[i] <= 90) - newStr += " "; + String newStr; + for(S32 i = 0; str[i]; i++) + { + //If capitol add a space + if(i != 0 && str[i] >= 65 && str[i] <= 90) + newStr += " "; - newStr += str[i]; - } + newStr += str[i]; + } - return Con::getReturnBuffer(newStr); + return Con::getReturnBuffer(newStr); } //---------------------------------------------------------------- @@ -1056,7 +1056,7 @@ DefineConsoleFunction( isInt, bool, ( const char* str),, "@endtsexample\n" "@ingroup Strings" ) { - return isInt(str); + return isInt(str); } //---------------------------------------------------------------- @@ -1071,7 +1071,7 @@ DefineConsoleFunction( isFloat, bool, ( const char* str, bool sciOk), (false), "@endtsexample\n" "@ingroup Strings" ) { - return isFloat(str, sciOk); + return isFloat(str, sciOk); } //---------------------------------------------------------------- @@ -1085,13 +1085,13 @@ DefineConsoleFunction( isValidPort, bool, ( const char* str),, "@endtsexample\n" "@ingroup Strings" ) { - if(isInt(str)) - { - U16 port = dAtous(str); - return isValidPort(port); - } - else - return false; + if(isInt(str)) + { + U16 port = dAtous(str); + return isValidPort(port); + } + else + return false; } //---------------------------------------------------------------- @@ -1105,12 +1105,12 @@ DefineConsoleFunction( isValidIP, bool, ( const char* str),, "@endtsexample\n" "@ingroup Strings" ) { - if(dStrcmp(str, "localhost") == 0) - { - return true; - } - else - return isValidIP(str); + if(dStrcmp(str, "localhost") == 0) + { + return true; + } + else + return isValidIP(str); } //---------------------------------------------------------------- @@ -1244,22 +1244,22 @@ DefineEngineFunction( monthNumToStr, String, ( S32 num, bool abbreviate ), (fals "@return month as a word given a number or \"\" if number is bad" "@ingroup FileSystem") { - switch(num) - { - case 1: return abbreviate ? "Jan" : "January"; break; - case 2: return abbreviate ? "Feb" : "February"; break; - case 3: return abbreviate ? "Mar" : "March"; break; - case 4: return abbreviate ? "Apr" : "April"; break; - case 5: return "May"; break; - case 6: return abbreviate ? "Jun" : "June"; break; - case 7: return abbreviate ? "Jul" : "July"; break; - case 8: return abbreviate ? "Aug" : "August"; break; - case 9: return abbreviate ? "Sep" : "September"; break; - case 10: return abbreviate ? "Oct" : "October"; break; - case 11: return abbreviate ? "Nov" : "November"; break; - case 12: return abbreviate ? "Dec" : "December"; break; - default: return ""; - } + switch(num) + { + case 1: return abbreviate ? "Jan" : "January"; break; + case 2: return abbreviate ? "Feb" : "February"; break; + case 3: return abbreviate ? "Mar" : "March"; break; + case 4: return abbreviate ? "Apr" : "April"; break; + case 5: return "May"; break; + case 6: return abbreviate ? "Jun" : "June"; break; + case 7: return abbreviate ? "Jul" : "July"; break; + case 8: return abbreviate ? "Aug" : "August"; break; + case 9: return abbreviate ? "Sep" : "September"; break; + case 10: return abbreviate ? "Oct" : "October"; break; + case 11: return abbreviate ? "Nov" : "November"; break; + case 12: return abbreviate ? "Dec" : "December"; break; + default: return ""; + } } DefineEngineFunction( weekdayNumToStr, String, ( S32 num, bool abbreviate ), (false), @@ -1267,17 +1267,17 @@ DefineEngineFunction( weekdayNumToStr, String, ( S32 num, bool abbreviate ), (fa "@return weekday as a word given a number or \"\" if number is bad" "@ingroup FileSystem") { - switch(num) - { - case 0: return abbreviate ? "Sun" : "Sunday"; break; - case 1: return abbreviate ? "Mon" : "Monday"; break; - case 2: return abbreviate ? "Tue" : "Tuesday"; break; - case 3: return abbreviate ? "Wed" : "Wednesday"; break; - case 4: return abbreviate ? "Thu" : "Thursday"; break; - case 5: return abbreviate ? "Fri" : "Friday"; break; - case 6: return abbreviate ? "Sat" : "Saturday"; break; - default: return ""; - } + switch(num) + { + case 0: return abbreviate ? "Sun" : "Sunday"; break; + case 1: return abbreviate ? "Mon" : "Monday"; break; + case 2: return abbreviate ? "Tue" : "Tuesday"; break; + case 3: return abbreviate ? "Wed" : "Wednesday"; break; + case 4: return abbreviate ? "Thu" : "Thursday"; break; + case 5: return abbreviate ? "Fri" : "Friday"; break; + case 6: return abbreviate ? "Sat" : "Saturday"; break; + default: return ""; + } } //----------------------------------------------------------------------------- @@ -3071,5 +3071,5 @@ DefineEngineFunction( getMaxDynamicVerts, S32, (),, "Get max number of allowable dynamic vertices in a single vertex buffer.\n\n" "@return the max number of allowable dynamic vertices in a single vertex buffer" ) { - return MAX_DYNAMIC_VERTS / 2; + return MAX_DYNAMIC_VERTS / 2; } \ No newline at end of file diff --git a/Engine/source/core/util/str.cpp b/Engine/source/core/util/str.cpp index 59fd0ab46..f97aa08fa 100644 --- a/Engine/source/core/util/str.cpp +++ b/Engine/source/core/util/str.cpp @@ -1627,100 +1627,100 @@ String String::GetTrailingNumber(const char* str, S32& number) String String::GetFirstNumber(const char* str, U32& startPos, U32& endPos) { - // Check for trivial strings - if (!str || !str[0]) - return String::EmptyString; + // Check for trivial strings + if (!str || !str[0]) + return String::EmptyString; - // Find the number at the end of the string - String base(str); - const char* p = base.c_str(); - const char* end = base.c_str() + base.length() - 1; - bool dec = false; - startPos = 0; + // Find the number at the end of the string + String base(str); + const char* p = base.c_str(); + const char* end = base.c_str() + base.length() - 1; + bool dec = false; + startPos = 0; - //Check if we are just a digit - if(p == end && isdigit(*p)) - return base; + //Check if we are just a digit + if(p == end && isdigit(*p)) + return base; - //Look for the first digit - while ((p != end) && (dIsspace(*p) || !isdigit(*p))) - { - p++; - startPos++; - } + //Look for the first digit + while ((p != end) && (dIsspace(*p) || !isdigit(*p))) + { + p++; + startPos++; + } - //Handle if we are at the end and found nothing - if(p == end && !isdigit(*p)) - return ""; + //Handle if we are at the end and found nothing + if(p == end && !isdigit(*p)) + return ""; - //update our end position at least to the start of our number - endPos = startPos; + //update our end position at least to the start of our number + endPos = startPos; - //Backup our ptr - const char* backup = p; + //Backup our ptr + const char* backup = p; - //Check for any negative or decimal values - if(startPos > 0) - { - p--; - startPos--; - if(*p == '.') - { - dec = true; + //Check for any negative or decimal values + if(startPos > 0) + { + p--; + startPos--; + if(*p == '.') + { + dec = true; - //ignore any duplicate periods - while ((p != base.c_str()) && (*p == '.')) - { - p--; - startPos--; - } + //ignore any duplicate periods + while ((p != base.c_str()) && (*p == '.')) + { + p--; + startPos--; + } - //Found a decimal lets still check for negative sign - if(startPos > 0) - { - p--; - startPos--; - if((*p != '-') && (*p != '_')) - { - startPos++; - p++; - } - } - } - else if((*p != '-') && (*p != '_')) - { - //go back to where we where cause no decimal or negative sign found - startPos++; - p++; - } - } + //Found a decimal lets still check for negative sign + if(startPos > 0) + { + p--; + startPos--; + if((*p != '-') && (*p != '_')) + { + startPos++; + p++; + } + } + } + else if((*p != '-') && (*p != '_')) + { + //go back to where we where cause no decimal or negative sign found + startPos++; + p++; + } + } - //Restore where we were - p = backup; + //Restore where we were + p = backup; - //look for the end of the digits - bool justFoundDec = false; - while (p != end) - { - if(*p == '.') - { - if(dec && !justFoundDec) - break; - else - { - dec = true; - justFoundDec = true; - } - } - else if(!isdigit(*p)) - break; - else if(justFoundDec) - justFoundDec = false; + //look for the end of the digits + bool justFoundDec = false; + while (p != end) + { + if(*p == '.') + { + if(dec && !justFoundDec) + break; + else + { + dec = true; + justFoundDec = true; + } + } + else if(!isdigit(*p)) + break; + else if(justFoundDec) + justFoundDec = false; - p++; - endPos++; - } + p++; + endPos++; + } - U32 len = (!isdigit(*p)) ? endPos - startPos : (endPos + 1) - startPos; - return base.substr(startPos, len); + U32 len = (!isdigit(*p)) ? endPos - startPos : (endPos + 1) - startPos; + return base.substr(startPos, len); } diff --git a/Engine/source/math/mConsoleFunctions.cpp b/Engine/source/math/mConsoleFunctions.cpp index f9b352c28..679dad3e2 100644 --- a/Engine/source/math/mConsoleFunctions.cpp +++ b/Engine/source/math/mConsoleFunctions.cpp @@ -96,16 +96,16 @@ DefineConsoleFunction( mFloor, S32, ( F32 v ),, DefineConsoleFunction( mRound, F32, ( F32 v, S32 n ), (0), - "Round v to the nth decimal place or the nearest whole number by default." - "@param v Value to round\n" - "@param n Number of decimal places to round to, 0 by default\n" - "@return The rounded value as a S32." - "@ingroup Math" ) + "Round v to the nth decimal place or the nearest whole number by default." + "@param v Value to round\n" + "@param n Number of decimal places to round to, 0 by default\n" + "@return The rounded value as a S32." + "@ingroup Math" ) { - if(n <= 0) - return mRound(v); - else - return mRound(v, n); + if(n <= 0) + return mRound(v); + else + return mRound(v, n); } DefineConsoleFunction( mCeil, S32, ( F32 v ),, From 2e7018bf681e8c5733cab28cac11f7e2de16e703 Mon Sep 17 00:00:00 2001 From: Nathan Bowhay Date: Wed, 11 Feb 2015 10:55:30 -0800 Subject: [PATCH 022/109] Added type conversions to fix compile errors Added two type conversions that were missing in order to fix some compile errors. --- Engine/source/core/strings/stringFunctions.h | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/Engine/source/core/strings/stringFunctions.h b/Engine/source/core/strings/stringFunctions.h index 0c11d73ed..89601319a 100644 --- a/Engine/source/core/strings/stringFunctions.h +++ b/Engine/source/core/strings/stringFunctions.h @@ -150,11 +150,20 @@ inline U32 dAtoui(const char *str, U32 base = 10) return strtoul(str, NULL, base); } +inline U16 dAtous(const char *str, U32 base = 10) +{ + return strtoul(str, NULL, base); +} + inline F32 dAtof(const char *str) { return strtof(str, NULL); } +inline F64 dAtod(const char *str) +{ + return strtod(str, NULL); +} inline char dToupper(const char c) { From 0915a642a5f96d6076047aa7e2243ea9a4bccb5f Mon Sep 17 00:00:00 2001 From: Nathan Bowhay Date: Thu, 12 Feb 2015 12:07:55 -0800 Subject: [PATCH 023/109] Optimized file reading to buffer Optimized the code that reads the file to the buffer as suggested by @jamesu --- Engine/source/app/net/tcpObject.cpp | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/Engine/source/app/net/tcpObject.cpp b/Engine/source/app/net/tcpObject.cpp index 28e91ba45..ac21cd43b 100644 --- a/Engine/source/app/net/tcpObject.cpp +++ b/Engine/source/app/net/tcpObject.cpp @@ -416,11 +416,7 @@ bool TCPObject::sendFile(const char* fileName) //Read each byte into our buffer Vector buffer(readFile.getStreamSize()); - U8 byte; - while(readFile.read(&byte)) - { - buffer.push_back(byte); - } + readFile.read(buffer.size(), &buffer); //Send the buffer send(buffer.address(), buffer.size()); From 1d20f6d26a1fe7f438e0f3540e35b083b843e25b Mon Sep 17 00:00:00 2001 From: Nathan Bowhay Date: Thu, 12 Feb 2015 12:18:34 -0800 Subject: [PATCH 024/109] Fixed spacing issues Changed spacing from tabs to 3 spaces. --- Engine/source/app/net/tcpObject.cpp | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/Engine/source/app/net/tcpObject.cpp b/Engine/source/app/net/tcpObject.cpp index ac21cd43b..d1f127924 100644 --- a/Engine/source/app/net/tcpObject.cpp +++ b/Engine/source/app/net/tcpObject.cpp @@ -407,21 +407,21 @@ void TCPObject::send(const U8 *buffer, U32 len) bool TCPObject::sendFile(const char* fileName) { - //Open the file for reading - FileStream readFile; - if(!readFile.open(fileName, Torque::FS::File::Read)) - { - return false; - } + //Open the file for reading + FileStream readFile; + if(!readFile.open(fileName, Torque::FS::File::Read)) + { + return false; + } - //Read each byte into our buffer - Vector buffer(readFile.getStreamSize()); - readFile.read(buffer.size(), &buffer); + //Read each byte into our buffer + Vector buffer(readFile.getStreamSize()); + readFile.read(buffer.size(), &buffer); - //Send the buffer - send(buffer.address(), buffer.size()); + //Send the buffer + send(buffer.address(), buffer.size()); - return true; + return true; } DefineEngineMethod(TCPObject, send, void, (const char *data),, @@ -450,13 +450,13 @@ DefineEngineMethod(TCPObject, sendFile, bool, (const char *fileName),, "@param fileName The filename of the file to transfer.\n") { - return object->sendFile(fileName); + return object->sendFile(fileName); } DefineEngineMethod(TCPObject, finishLastLine, void, (),, "@brief Eat the rest of the lines.\n") { - object->finishLastLine(); + object->finishLastLine(); } DefineEngineMethod(TCPObject, listen, void, (U32 port),, From 69b1c0072cb7a492f20123be4139733b6ab4c668 Mon Sep 17 00:00:00 2001 From: Nathan Bowhay Date: Thu, 12 Feb 2015 12:19:56 -0800 Subject: [PATCH 025/109] Allowed for WebSocket reading Allowed for WebSocket reading in script. --- Engine/source/app/net/tcpObject.cpp | 47 ++++++++++++++++++++++++++++- Engine/source/app/net/tcpObject.h | 5 ++- 2 files changed, 50 insertions(+), 2 deletions(-) diff --git a/Engine/source/app/net/tcpObject.cpp b/Engine/source/app/net/tcpObject.cpp index d1f127924..06f320576 100644 --- a/Engine/source/app/net/tcpObject.cpp +++ b/Engine/source/app/net/tcpObject.cpp @@ -139,6 +139,11 @@ IMPLEMENT_CALLBACK(TCPObject, onLine, void, (const char* line), (line), "@param line Data sent from the server.\n" ); +IMPLEMENT_CALLBACK(TCPObject, onPacket, bool, (const char* data), (data), + "@brief Called when we get a packet with no newlines or nulls (probably websocket).\n\n" + "@param data Data sent from the server.\n" + "@return true if script handled the packet.\n" + ); IMPLEMENT_CALLBACK(TCPObject, onEndReceive, void, (), (), "@brief Called when we are done reading all lines.\n\n" ); @@ -360,7 +365,7 @@ void TCPObject::onConnectFailed() onConnectFailed_callback(); } -void TCPObject::finishLastLine() +bool TCPObject::finishLastLine() { if(mBufferSize) { @@ -369,6 +374,25 @@ void TCPObject::finishLastLine() dFree(mBuffer); mBuffer = 0; mBufferSize = 0; + + return true; + } + + return false; +} + +bool TCPObject::isBufferEmpty() +{ + return (mBufferSize <= 0); +} + +void TCPObject::emptyBuffer() +{ + if(mBufferSize) + { + dFree(mBuffer); + mBuffer = 0; + mBufferSize = 0; } } @@ -538,6 +562,27 @@ void processConnectedReceiveEvent(NetSocket sock, RawData incomingData) buffer += ret; } + //If our buffer now has something in it then it's probably a web socket packet and lets handle it + if(!tcpo->isBufferEmpty()) + { + //Copy all the data into a string (may be a quicker way of doing this) + U8 *data = (U8*)incomingData.data; + String temp; + for(S32 i = 0; i < incomingData.size; i++) + { + temp += data[i]; + } + + //Send the packet to script + bool handled = tcpo->onPacket_callback(temp); + + //If the script did something with it, clear the buffer + if(handled) + { + tcpo->emptyBuffer(); + } + } + tcpo->onEndReceive_callback(); } diff --git a/Engine/source/app/net/tcpObject.h b/Engine/source/app/net/tcpObject.h index 39b9464ba..9a8b5e40d 100644 --- a/Engine/source/app/net/tcpObject.h +++ b/Engine/source/app/net/tcpObject.h @@ -36,6 +36,7 @@ public: DECLARE_CALLBACK(void, onConnectionRequest, (const char* address, const char* ID)); DECLARE_CALLBACK(void, onLine, (const char* line)); + DECLARE_CALLBACK(bool, onPacket, (const char* data)); DECLARE_CALLBACK(void, onEndReceive, ()); DECLARE_CALLBACK(void, onDNSResolved,()); DECLARE_CALLBACK(void, onDNSFailed, ()); @@ -61,7 +62,9 @@ public: virtual ~TCPObject(); void parseLine(U8 *buffer, U32 *start, U32 bufferLen); - void finishLastLine(); + bool finishLastLine(); + bool isBufferEmpty(); + void emptyBuffer(); static TCPObject *find(NetSocket tag); From cd686a23b3b39c18552c933f76bc6587fc5ce2f3 Mon Sep 17 00:00:00 2001 From: Miodrag Sejic Date: Sat, 28 Feb 2015 21:28:10 +0100 Subject: [PATCH 026/109] +proper buffer size for udp socket MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit SkypeLog: 28.02.2015 [17:18:32] DuÅ¡an Jocic: when creating user datagram protocol or UDP, old implementation was limiting lan utalization to 1% of our modern 1000Mbps hardwar [17:19:15] DuÅ¡an Jocic: that add proper buffer size for udp socket [17:19:43] DuÅ¡an Jocic: and lift off limitation what was present there for 10/100 mbps lan --- Engine/source/platform/platformNet.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Engine/source/platform/platformNet.cpp b/Engine/source/platform/platformNet.cpp index acc4e9905..5f7adb856 100644 --- a/Engine/source/platform/platformNet.cpp +++ b/Engine/source/platform/platformNet.cpp @@ -503,7 +503,7 @@ bool Net::openPort(S32 port, bool doBind) } if(error == NoError) - error = setBufferSize(udpSocket, 32768); + error = setBufferSize(udpSocket, 32768*8); if(error == NoError && !useVDP) error = setBroadcast(udpSocket, true); From 6b0c6ae8ac79b4841c24f00c6066d04c61e4dda8 Mon Sep 17 00:00:00 2001 From: Azaezel Date: Wed, 24 Jun 2015 14:26:23 -0500 Subject: [PATCH 027/109] adds 3 new horizontal relative gui entries for dealing with swapping elements between 4:3 and 16:9 aspect ratios. "relativeToYL" maintains the initial aspect ratio, keeping the leftmost edge the same, "relativeToYR" also shifts it so the right hand side is shifted over so that edge would match, and "relativeToYC" takes/adds space from both sides --- Engine/source/gui/core/guiControl.cpp | 33 +++++++++++++++++++++++++++ Engine/source/gui/core/guiControl.h | 3 +++ 2 files changed, 36 insertions(+) diff --git a/Engine/source/gui/core/guiControl.cpp b/Engine/source/gui/core/guiControl.cpp index a17e701f3..14a10c517 100644 --- a/Engine/source/gui/core/guiControl.cpp +++ b/Engine/source/gui/core/guiControl.cpp @@ -175,6 +175,9 @@ ImplementEnumType( GuiHorizontalSizing, { GuiControl::horizResizeLeft, "left" }, { GuiControl::horizResizeCenter, "center" }, { GuiControl::horizResizeRelative, "relative" }, + { GuiControl::horizResizeRelativeToYL, "relativeToYL" }, + { GuiControl::horizResizeRelativeToYR, "relativeToYR" }, + { GuiControl::horizResizeRelativeToYC, "relativeToYC" }, { GuiControl::horizResizeWindowRelative, "windowRelative" } EndImplementEnumType; @@ -1366,6 +1369,36 @@ void GuiControl::parentResized(const RectI &oldParentRect, const RectI &newParen newPosition.x = newLeft; newExtent.x = newWidth; } + else if (mHorizSizing == horizResizeRelativeToYL && oldParentRect.extent.x != 0) + { + S32 newLeft = mRoundToNearest((F32(newPosition.x) / F32(oldParentRect.extent.x)) * F32(newParentRect.extent.x)); + S32 newWidth = mRoundToNearest((F32(newExtent.x) / F32(oldParentRect.extent.y)) * F32(newParentRect.extent.y)); + + newPosition.x = newLeft; + newExtent.x = newWidth; + } + else if (mHorizSizing == horizResizeRelativeToYR && oldParentRect.extent.x != 0) + { + S32 newLeft = mRoundToNearest((F32(newPosition.x) / F32(oldParentRect.extent.x)) * F32(newParentRect.extent.x)); + S32 newWidth = mRoundToNearest((F32(newExtent.x) / F32(oldParentRect.extent.y)) * F32(newParentRect.extent.y)); //origional aspect ratio corrected width + S32 rWidth = mRoundToNearest((F32(newExtent.x) / F32(oldParentRect.extent.x)) * F32(newParentRect.extent.x)); //parent aspect ratio relative width + + S32 offset = rWidth - newWidth; // account for change in relative width + newLeft += offset; + newPosition.x = newLeft; + newExtent.x = newWidth; + } + else if (mHorizSizing == horizResizeRelativeToYC && oldParentRect.extent.x != 0) + { + S32 newLeft = mRoundToNearest((F32(newPosition.x) / F32(oldParentRect.extent.x)) * F32(newParentRect.extent.x)); + S32 newWidth = mRoundToNearest((F32(newExtent.x) / F32(oldParentRect.extent.y)) * F32(newParentRect.extent.y)); //origional aspect ratio corrected width + S32 rWidth = mRoundToNearest((F32(newExtent.x) / F32(oldParentRect.extent.x)) * F32(newParentRect.extent.x)); //parent aspect ratio relative width + + S32 offset = rWidth - newWidth; // account for change in relative width + newLeft += offset/2; + newPosition.x = newLeft; + newExtent.x = newWidth; + } if (mVertSizing == vertResizeCenter) newPosition.y = (newParentRect.extent.y - getHeight()) >> 1; diff --git a/Engine/source/gui/core/guiControl.h b/Engine/source/gui/core/guiControl.h index 4240fbad6..fd538c65f 100644 --- a/Engine/source/gui/core/guiControl.h +++ b/Engine/source/gui/core/guiControl.h @@ -121,6 +121,9 @@ class GuiControl : public SimGroup horizResizeLeft, ///< fixed on the right and width horizResizeCenter, horizResizeRelative, ///< resize relative + horizResizeRelativeToYL, ///< resize relative to hieght delta (offset Left) + horizResizeRelativeToYR, ///< resize relative to hieght delta (offset Right) + horizResizeRelativeToYC, ///< resize relative to hieght delta (offset Right) horizResizeWindowRelative ///< resize window relative }; enum vertSizingOptions From 138d34e31cc54a41f4fefae39cbc773365ba982d Mon Sep 17 00:00:00 2001 From: Azaezel Date: Wed, 24 Jun 2015 17:18:36 -0500 Subject: [PATCH 028/109] renames for clarity: relativeToYL changed to aspectLeft ect. also by request, vertsizing options for down the line when mobile is supported.. --- Engine/source/gui/core/guiControl.cpp | 45 +++++++++++++++++++++++---- Engine/source/gui/core/guiControl.h | 9 ++++-- 2 files changed, 45 insertions(+), 9 deletions(-) diff --git a/Engine/source/gui/core/guiControl.cpp b/Engine/source/gui/core/guiControl.cpp index 14a10c517..222560f75 100644 --- a/Engine/source/gui/core/guiControl.cpp +++ b/Engine/source/gui/core/guiControl.cpp @@ -175,9 +175,9 @@ ImplementEnumType( GuiHorizontalSizing, { GuiControl::horizResizeLeft, "left" }, { GuiControl::horizResizeCenter, "center" }, { GuiControl::horizResizeRelative, "relative" }, - { GuiControl::horizResizeRelativeToYL, "relativeToYL" }, - { GuiControl::horizResizeRelativeToYR, "relativeToYR" }, - { GuiControl::horizResizeRelativeToYC, "relativeToYC" }, + { GuiControl::horizResizeAspectLeft, "aspectLeft" }, + { GuiControl::horizResizeAspectRight, "aspectRight" }, + { GuiControl::horizResizeAspectCenter, "aspectCenter" }, { GuiControl::horizResizeWindowRelative, "windowRelative" } EndImplementEnumType; @@ -189,6 +189,9 @@ ImplementEnumType( GuiVerticalSizing, { GuiControl::vertResizeTop, "top" }, { GuiControl::vertResizeCenter, "center" }, { GuiControl::vertResizeRelative, "relative" }, + { GuiControl::vertResizeAspectTop, "aspectTop" }, + { GuiControl::vertResizeAspectBottom, "aspectBottom" }, + { GuiControl::vertResizeAspectCenter, "aspectCenter" }, { GuiControl::vertResizeWindowRelative, "windowRelative" } EndImplementEnumType; @@ -1369,7 +1372,7 @@ void GuiControl::parentResized(const RectI &oldParentRect, const RectI &newParen newPosition.x = newLeft; newExtent.x = newWidth; } - else if (mHorizSizing == horizResizeRelativeToYL && oldParentRect.extent.x != 0) + else if (mHorizSizing == horizResizeAspectLeft && oldParentRect.extent.x != 0) { S32 newLeft = mRoundToNearest((F32(newPosition.x) / F32(oldParentRect.extent.x)) * F32(newParentRect.extent.x)); S32 newWidth = mRoundToNearest((F32(newExtent.x) / F32(oldParentRect.extent.y)) * F32(newParentRect.extent.y)); @@ -1377,7 +1380,7 @@ void GuiControl::parentResized(const RectI &oldParentRect, const RectI &newParen newPosition.x = newLeft; newExtent.x = newWidth; } - else if (mHorizSizing == horizResizeRelativeToYR && oldParentRect.extent.x != 0) + else if (mHorizSizing == horizResizeAspectRight && oldParentRect.extent.x != 0) { S32 newLeft = mRoundToNearest((F32(newPosition.x) / F32(oldParentRect.extent.x)) * F32(newParentRect.extent.x)); S32 newWidth = mRoundToNearest((F32(newExtent.x) / F32(oldParentRect.extent.y)) * F32(newParentRect.extent.y)); //origional aspect ratio corrected width @@ -1388,7 +1391,7 @@ void GuiControl::parentResized(const RectI &oldParentRect, const RectI &newParen newPosition.x = newLeft; newExtent.x = newWidth; } - else if (mHorizSizing == horizResizeRelativeToYC && oldParentRect.extent.x != 0) + else if (mHorizSizing == horizResizeAspectCenter && oldParentRect.extent.x != 0) { S32 newLeft = mRoundToNearest((F32(newPosition.x) / F32(oldParentRect.extent.x)) * F32(newParentRect.extent.x)); S32 newWidth = mRoundToNearest((F32(newExtent.x) / F32(oldParentRect.extent.y)) * F32(newParentRect.extent.y)); //origional aspect ratio corrected width @@ -1414,6 +1417,36 @@ void GuiControl::parentResized(const RectI &oldParentRect, const RectI &newParen newPosition.y = newTop; newExtent.y = newHeight; } + else if (mVertSizing == vertResizeAspectTop && oldParentRect.extent.y != 0) + { + S32 newTop = mRoundToNearest((F32(newPosition.y) / F32(oldParentRect.extent.y)) * F32(newParentRect.extent.y)); + S32 newHeight = mRoundToNearest((F32(newExtent.y) / F32(oldParentRect.extent.x)) * F32(newParentRect.extent.x)); + + newPosition.y = newTop; + newExtent.y = newHeight; + } + else if (mVertSizing == vertResizeAspectBottom && oldParentRect.extent.y != 0) + { + S32 newTop = mRoundToNearest((F32(newPosition.y) / F32(oldParentRect.extent.y)) * F32(newParentRect.extent.y)); + S32 newHeight = mRoundToNearest((F32(newExtent.y) / F32(oldParentRect.extent.x)) * F32(newParentRect.extent.x)); //origional aspect ratio corrected hieght + S32 rHeight = mRoundToNearest((F32(newExtent.y) / F32(oldParentRect.extent.y)) * F32(newParentRect.extent.y)); //parent aspect ratio relative hieght + + S32 offset = rHeight - newHeight; // account for change in relative hieght + newTop += offset; + newPosition.y = newTop; + newExtent.y = newHeight; + } + else if (mVertSizing == vertResizeAspectCenter && oldParentRect.extent.y != 0) + { + S32 newTop = mRoundToNearest((F32(newPosition.y) / F32(oldParentRect.extent.y)) * F32(newParentRect.extent.y)); + S32 newHeight = mRoundToNearest((F32(newExtent.y) / F32(oldParentRect.extent.x)) * F32(newParentRect.extent.x)); //origional aspect ratio corrected hieght + S32 rHeight = mRoundToNearest((F32(newExtent.y) / F32(oldParentRect.extent.y)) * F32(newParentRect.extent.y)); //parent aspect ratio relative hieght + + S32 offset = rHeight - newHeight; // account for change in relative hieght + newTop += offset / 2; + newPosition.y = newTop; + newExtent.y = newHeight; + } // Resizing Re factor [9/18/2006] // Only resize if our minExtent is satisfied with it. diff --git a/Engine/source/gui/core/guiControl.h b/Engine/source/gui/core/guiControl.h index fd538c65f..dc2653ff9 100644 --- a/Engine/source/gui/core/guiControl.h +++ b/Engine/source/gui/core/guiControl.h @@ -121,9 +121,9 @@ class GuiControl : public SimGroup horizResizeLeft, ///< fixed on the right and width horizResizeCenter, horizResizeRelative, ///< resize relative - horizResizeRelativeToYL, ///< resize relative to hieght delta (offset Left) - horizResizeRelativeToYR, ///< resize relative to hieght delta (offset Right) - horizResizeRelativeToYC, ///< resize relative to hieght delta (offset Right) + horizResizeAspectLeft, ///< resize relative to hieght delta (offset Left) + horizResizeAspectRight, ///< resize relative to hieght delta (offset Right) + horizResizeAspectCenter, ///< resize relative to hieght delta (Centered) horizResizeWindowRelative ///< resize window relative }; enum vertSizingOptions @@ -133,6 +133,9 @@ class GuiControl : public SimGroup vertResizeTop, ///< fixed in height and on the bottom vertResizeCenter, vertResizeRelative, ///< resize relative + vertResizeAspectTop, ///< resize relative to width delta (offset Left) + vertResizeAspectBottom, ///< resize relative to width delta (offset Right) + vertResizeAspectCenter, ///< resize relative to width delta Centered) vertResizeWindowRelative ///< resize window relative }; From 03109c9d6d613d51902fa8451fc862acf2214af7 Mon Sep 17 00:00:00 2001 From: Lopuska Date: Wed, 21 Jan 2015 23:14:53 +0100 Subject: [PATCH 029/109] Color Picker --- Engine/source/console/consoleFunctions.cpp | 83 ++ Engine/source/core/color.h | 284 ++++ Engine/source/gui/controls/guiColorPicker.cpp | 229 +++- Engine/source/gui/controls/guiColorPicker.h | 8 +- .../source/gui/controls/guiTextEditCtrl.cpp | 101 +- Engine/source/gui/controls/guiTextEditCtrl.h | 6 + Engine/source/math/mConsoleFunctions.cpp | 13 + .../Empty/game/tools/gui/colorPicker.ed.gui | 1171 +++++++++++++---- .../Full/game/tools/gui/colorPicker.ed.gui | 1171 +++++++++++++---- 9 files changed, 2424 insertions(+), 642 deletions(-) diff --git a/Engine/source/console/consoleFunctions.cpp b/Engine/source/console/consoleFunctions.cpp index a5ea49f33..1e72f05b8 100644 --- a/Engine/source/console/consoleFunctions.cpp +++ b/Engine/source/console/consoleFunctions.cpp @@ -33,6 +33,9 @@ #include "platform/platformInput.h" #include "core/util/journal/journal.h" #include "core/util/uuid.h" +#include "core/color.h" +#include "math/mPoint3.h" +#include "math/mathTypes.h" #ifdef TORQUE_DEMO_PURCHASE #include "gui/core/guiCanvas.h" @@ -815,6 +818,86 @@ DefineConsoleFunction( strrchrpos, S32, ( const char* str, const char* chr, S32 return index; } +//---------------------------------------------------------------- + +DefineConsoleFunction(ColorFloatToInt, ColorI, (ColorF color), , + "Convert from a float color to an integer color (0.0 - 1.0 to 0 to 255).\n" + "@param color Float color value to be converted in the form \"R G B A\", where R is red, G is green, B is blue, and A is alpha.\n" + "@return Converted color value (0 - 255)\n\n" + "@tsexample\n" + "ColorFloatToInt( \"0 0 1 0.5\" ) // Returns \"0 0 255 128\".\n" + "@endtsexample\n" + "@ingroup Strings") +{ + return (ColorI)color; +} + +DefineConsoleFunction(ColorIntToFloat, ColorF, (ColorI color), , + "Convert from a integer color to an float color (0 to 255 to 0.0 - 1.0).\n" + "@param color Integer color value to be converted in the form \"R G B A\", where R is red, G is green, B is blue, and A is alpha.\n" + "@return Converted color value (0.0 - 1.0)\n\n" + "@tsexample\n" + "ColorIntToFloat( \"0 0 255 128\" ) // Returns \"0 0 1 0.5\".\n" + "@endtsexample\n" + "@ingroup Strings") +{ + return (ColorF)color; +} + +DefineConsoleFunction(ColorRGBToHEX, const char*, (ColorI color), , + "Convert from a integer RGB (red, green, blue) color to hex color value (0 to 255 to 00 - FF).\n" + "@param color Integer color value to be converted in the form \"R G B A\", where R is red, G is green, B is blue, and A is alpha. It excepts an alpha, but keep in mind this will not be converted.\n" + "@return Hex color value (#000000 - #FFFFFF), alpha isn't handled/converted so it is only the RGB value\n\n" + "@tsexample\n" + "ColorRBGToHEX( \"0 0 255 128\" ) // Returns \"#0000FF\".\n" + "@endtsexample\n" + "@ingroup Strings") +{ + return Con::getReturnBuffer(color.getHex()); +} + +DefineConsoleFunction(ColorRGBToHSB, const char*, (ColorI color), , + "Convert from a integer RGB (red, green, blue) color to HSB (hue, saturation, brightness). HSB is also know as HSL or HSV as well, with the last letter standing for lightness or value.\n" + "@param color Integer color value to be converted in the form \"R G B A\", where R is red, G is green, B is blue, and A is alpha. It excepts an alpha, but keep in mind this will not be converted.\n" + "@return HSB color value, alpha isn't handled/converted so it is only the RGB value\n\n" + "@tsexample\n" + "ColorRBGToHSB( \"0 0 255 128\" ) // Returns \"240 100 100\".\n" + "@endtsexample\n" + "@ingroup Strings") +{ + ColorI::Hsb hsb(color.getHSB()); + String s(String::ToString(hsb.hue) + " " + String::ToString(hsb.sat) + " " + String::ToString(hsb.brightness)); + return Con::getReturnBuffer(s); +} + +DefineConsoleFunction(ColorHEXToRGB, ColorI, (const char* hex), , + "Convert from a hex color value to an integer RGB (red, green, blue) color (00 - FF to 0 to 255).\n" + "@param hex Hex color value (#000000 - #FFFFFF) to be converted to an RGB (red, green, blue) value.\n" + "@return Integer color value to be converted in the form \"R G B A\", where R is red, G is green, B is blue, and A is alpha. Alpha isn't handled/converted so only pay attention to the RGB value\n\n" + "@tsexample\n" + "ColorHEXToRGB( \"#0000FF\" ) // Returns \"0 0 255 0\".\n" + "@endtsexample\n" + "@ingroup Strings") +{ + ColorI color; + color.set(hex); + return color; +} + +DefineConsoleFunction(ColorHSBToRGB, ColorI, (Point3I hsb), , + "Convert from a HSB (hue, saturation, brightness) to an integer RGB (red, green, blue) color. HSB is also know as HSL or HSV as well, with the last letter standing for lightness or value.\n" + "@param hsb HSB (hue, saturation, brightness) value to be converted.\n" + "@return Integer color value to be converted in the form \"R G B A\", where R is red, G is green, B is blue, and A is alpha. Alpha isn't handled/converted so only pay attention to the RGB value\n\n" + "@tsexample\n" + "ColorHSBToRGB( \"240 100 100\" ) // Returns \"0 0 255 0\".\n" + "@endtsexample\n" + "@ingroup Strings") +{ + ColorI color; + color.set(ColorI::Hsb(hsb.x, hsb.y, hsb.z)); + return color; +} + //============================================================================= // Field Manipulators. //============================================================================= diff --git a/Engine/source/core/color.h b/Engine/source/core/color.h index b0620fc4f..ddb98c8a8 100644 --- a/Engine/source/core/color.h +++ b/Engine/source/core/color.h @@ -30,6 +30,10 @@ #include "math/mPoint4.h" #endif +#ifndef _ENGINEAPI_H_ +#include "console/engineAPI.h" +#endif + class ColorI; @@ -121,9 +125,20 @@ class ColorI U8 blue; U8 alpha; + struct Hsb + { + Hsb() :hue(0), sat(0), brightness(0){}; + Hsb(U32 h, U32 s, U32 b) :hue(h), sat(s), brightness(b){}; + + U32 hue; ///Hue + U32 sat; ///Saturation + U32 brightness; //Brightness/Value/Lightness + }; + public: ColorI() { } ColorI(const ColorI& in_rCopy); + ColorI(const Hsb& color); ColorI(const U8 in_r, const U8 in_g, const U8 in_b, @@ -132,6 +147,12 @@ class ColorI ColorI( const char* pStockColorName ); + void set(const Hsb& color); + + void HSLtoRGB_Subfunction(U32& c, const F64& temp1, const F64& temp2, const F64& temp3); + + void set(const String& hex); + void set(const U8 in_r, const U8 in_g, const U8 in_b, @@ -176,6 +197,11 @@ class ColorI U16 get565() const; U16 get4444() const; + Hsb getHSB() const; + + String getHex() const; + S32 convertFromHex(const String& hex) const; + operator ColorF() const; operator const U8*() const { return &red; } @@ -459,6 +485,174 @@ inline void ColorI::set(const ColorI& in_rCopy, alpha = in_a; } +inline void ColorI::set(const Hsb& color) +{ + U32 r = 0; + U32 g = 0; + U32 b = 0; + + F64 L = ((F64)color.brightness) / 100.0; + F64 S = ((F64)color.sat) / 100.0; + F64 H = ((F64)color.hue) / 360.0; + + if (color.sat == 0) + { + r = color.brightness; + g = color.brightness; + b = color.brightness; + } + else + { + F64 temp1 = 0; + if (L < 0.50) + { + temp1 = L*(1 + S); + } + else + { + temp1 = L + S - (L*S); + } + + F64 temp2 = 2.0*L - temp1; + + F64 temp3 = 0; + for (S32 i = 0; i < 3; i++) + { + switch (i) + { + case 0: // red + { + temp3 = H + 0.33333; + if (temp3 > 1.0) + temp3 -= 1.0; + HSLtoRGB_Subfunction(r, temp1, temp2, temp3); + break; + } + case 1: // green + { + temp3 = H; + HSLtoRGB_Subfunction(g, temp1, temp2, temp3); + break; + } + case 2: // blue + { + temp3 = H - 0.33333; + if (temp3 < 0) + temp3 += 1; + HSLtoRGB_Subfunction(b, temp1, temp2, temp3); + break; + } + default: + { + + } + } + } + } + red = (U32)((((F64)r) / 100) * 255); + green = (U32)((((F64)g) / 100) * 255); + blue = (U32)((((F64)b) / 100) * 255); +} + +// This is a subfunction of HSLtoRGB +inline void ColorI::HSLtoRGB_Subfunction(U32& c, const F64& temp1, const F64& temp2, const F64& temp3) +{ + if ((temp3 * 6.0) < 1.0) + c = (U32)((temp2 + (temp1 - temp2)*6.0*temp3)*100.0); + else + if ((temp3 * 2.0) < 1.0) + c = (U32)(temp1*100.0); + else + if ((temp3 * 3.0) < 2.0) + c = (U32)((temp2 + (temp1 - temp2)*(0.66666 - temp3)*6.0)*100.0); + else + c = (U32)(temp2*100.0); + return; +} + +inline void ColorI::set(const String& hex) +{ + String redString; + String greenString; + String blueString; + + //if the prefix # was attached to hex + if (hex[0] == '#') + { + redString = hex.substr(1, 2); + greenString = hex.substr(3, 2); + blueString = hex.substr(5, 2); + } + else + { + // since there is no prefix attached to hex + redString = hex.substr(0, 2); + greenString = hex.substr(2, 2); + blueString = hex.substr(4, 2); + } + + red = (U8)(convertFromHex(redString)); + green = (U8)(convertFromHex(greenString)); + blue = (U8)(convertFromHex(blueString)); +} + +inline S32 ColorI::convertFromHex(const String& hex) const +{ + S32 hexValue = 0; + + S32 a = 0; + S32 b = hex.length() - 1; + + for (; b >= 0; a++, b--) + { + if (hex[b] >= '0' && hex[b] <= '9') + { + hexValue += (hex[b] - '0') * (1 << (a * 4)); + } + else + { + switch (hex[b]) + { + case 'A': + case 'a': + hexValue += 10 * (1 << (a * 4)); + break; + + case 'B': + case 'b': + hexValue += 11 * (1 << (a * 4)); + break; + + case 'C': + case 'c': + hexValue += 12 * (1 << (a * 4)); + break; + + case 'D': + case 'd': + hexValue += 13 * (1 << (a * 4)); + break; + + case 'E': + case 'e': + hexValue += 14 * (1 << (a * 4)); + break; + + case 'F': + case 'f': + hexValue += 15 * (1 << (a * 4)); + break; + + default: + Con::errorf("Error, invalid character '%c' in hex number", hex[a]); + break; + } + } + } + + return hexValue; +} + inline ColorI::ColorI(const ColorI& in_rCopy) { red = in_rCopy.red; @@ -467,6 +661,11 @@ inline ColorI::ColorI(const ColorI& in_rCopy) alpha = in_rCopy.alpha; } +inline ColorI::ColorI(const Hsb& color) +{ + set(color); +} + inline ColorI::ColorI(const U8 in_r, const U8 in_g, const U8 in_b, @@ -647,6 +846,91 @@ inline U16 ColorI::get4444() const U16(U16(blue >> 4) << 0)); } +inline ColorI::Hsb ColorI::getHSB() const +{ + F64 rPercent = ((F64)red) / 255; + F64 gPercent = ((F64)green) / 255; + F64 bPercent = ((F64)blue) / 255; + + F64 maxColor = 0.0; + if ((rPercent >= gPercent) && (rPercent >= bPercent)) + maxColor = rPercent; + if ((gPercent >= rPercent) && (gPercent >= bPercent)) + maxColor = gPercent; + if ((bPercent >= rPercent) && (bPercent >= gPercent)) + maxColor = bPercent; + + F64 minColor = 0.0; + if ((rPercent <= gPercent) && (rPercent <= bPercent)) + minColor = rPercent; + if ((gPercent <= rPercent) && (gPercent <= bPercent)) + minColor = gPercent; + if ((bPercent <= rPercent) && (bPercent <= gPercent)) + minColor = bPercent; + + F64 H = 0.0; + F64 S = 0.0; + F64 B = 0.0; + + B = (maxColor + minColor) / 2.0; + + if (maxColor == minColor) + { + H = 0.0; + S = 0.0; + } + else + { + if (B < 0.50) + { + S = (maxColor - minColor) / (maxColor + minColor); + } + else + { + S = (maxColor - minColor) / (2.0 - maxColor - minColor); + } + if (maxColor == rPercent) + { + H = (gPercent - bPercent) / (maxColor - minColor); + } + if (maxColor == gPercent) + { + H = 2.0 + (bPercent - rPercent) / (maxColor - minColor); + } + if (maxColor == bPercent) + { + H = 4.0 + (rPercent - gPercent) / (maxColor - minColor); + } + } + + ColorI::Hsb val; + val.sat = (U32)(S * 100); + val.brightness = (U32)(B * 100); + H = H*60.0; + if (H < 0.0) + H += 360.0; + val.hue = (U32)H; + + return val; +} + +inline String ColorI::getHex() const +{ + char r[255]; + dSprintf(r, sizeof(r), "%.2X", red); + String result(r); + + char g[255]; + dSprintf(g, sizeof(g), "%.2X", green); + result += g; + + char b[255]; + dSprintf(b, sizeof(b), "%.2X", blue); + result += b; + + return result; +} + //-------------------------------------- INLINE CONVERSION OPERATORS inline ColorF::operator ColorI() const { diff --git a/Engine/source/gui/controls/guiColorPicker.cpp b/Engine/source/gui/controls/guiColorPicker.cpp index 0b8676240..39c140434 100644 --- a/Engine/source/gui/controls/guiColorPicker.cpp +++ b/Engine/source/gui/controls/guiColorPicker.cpp @@ -71,6 +71,18 @@ GuiColorPickerCtrl::GuiColorPickerCtrl() mSelectorGap = 1; mActionOnMove = false; mShowReticle = true; + mSelectColor = false; + mSetColor = mSetColor.BLACK; + mBitmap = NULL; +} + +GuiColorPickerCtrl::~GuiColorPickerCtrl() +{ + if (mBitmap) + { + delete mBitmap; + mBitmap = NULL; + } } //-------------------------------------------------------------------------- @@ -331,60 +343,180 @@ void GuiColorPickerCtrl::renderColorBox(RectI &bounds) void GuiColorPickerCtrl::onRender(Point2I offset, const RectI& updateRect) { - if (mStateBlock.isNull()) - { - GFXStateBlockDesc desc; - desc.setBlend(true, GFXBlendSrcAlpha, GFXBlendInvSrcAlpha); - desc.setZReadWrite(false); - desc.zWriteEnable = false; - desc.setCullMode(GFXCullNone); - mStateBlock = GFX->createStateBlock( desc ); - } + if (mStateBlock.isNull()) + { + GFXStateBlockDesc desc; + desc.setBlend(true, GFXBlendSrcAlpha, GFXBlendInvSrcAlpha); + desc.setZReadWrite(false); + desc.zWriteEnable = false; + desc.setCullMode(GFXCullNone); + mStateBlock = GFX->createStateBlock(desc); + } - RectI boundsRect(offset, getExtent()); - renderColorBox(boundsRect); + RectI boundsRect(offset, getExtent()); + renderColorBox(boundsRect); - if (mPositionChanged) - { - mPositionChanged = false; - Point2I extent = getRoot()->getExtent(); - // If we are anything but a pallete, change the pick color - if (mDisplayMode != pPallet) - { - Point2I resolution = getRoot()->getExtent(); + if (mPositionChanged || mBitmap == NULL) + { + bool nullBitmap = false; - U32 buf_x = offset.x + mSelectorPos.x + 1; - U32 buf_y = resolution.y - ( extent.y - ( offset.y + mSelectorPos.y + 1 ) ); + if (mPositionChanged == false && mBitmap == NULL) + nullBitmap = true; - GFXTexHandle bb( resolution.x, - resolution.y, - GFXFormatR8G8B8A8, &GFXDefaultRenderTargetProfile, avar("%s() - bb (line %d)", __FUNCTION__, __LINE__) ); - - Point2I tmpPt( buf_x, buf_y ); + mPositionChanged = false; + Point2I extent = getRoot()->getExtent(); + // If we are anything but a pallete, change the pick color + if (mDisplayMode != pPallet) + { + Point2I resolution = getRoot()->getExtent(); - GFXTarget *targ = GFX->getActiveRenderTarget(); - targ->resolveTo( bb ); - - GBitmap bmp( bb.getWidth(), bb.getHeight() ); + U32 buf_x = offset.x + mSelectorPos.x + 1; + U32 buf_y = resolution.y - (extent.y - (offset.y + mSelectorPos.y + 1)); - bb.copyToBmp( &bmp ); - - //bmp.writePNGDebug( "foo.png" ); + GFXTexHandle bb(resolution.x, + resolution.y, + GFXFormatR8G8B8A8, &GFXDefaultRenderTargetProfile, avar("%s() - bb (line %d)", __FUNCTION__, __LINE__)); - ColorI tmp; - bmp.getColor( buf_x, buf_y, tmp ); + Point2I tmpPt(buf_x, buf_y); - mPickColor = (ColorF)tmp; + GFXTarget *targ = GFX->getActiveRenderTarget(); + targ->resolveTo(bb); - // Now do onAction() if we are allowed - if (mActionOnMove) - onAction(); - } - - } - - //render the children - renderChildControls( offset, updateRect); + if (mBitmap) + { + delete mBitmap; + mBitmap = NULL; + } + + mBitmap = new GBitmap(bb.getWidth(), bb.getHeight()); + + bb.copyToBmp(mBitmap); + + //bmp.writePNGDebug( "foo.png" ); + + if (!nullBitmap) + { + if (mSelectColor) + { + Point2I pos = findColor(mSetColor, offset, resolution, *mBitmap); + mSetColor = mSetColor.BLACK; + mSelectColor = false; + + setSelectorPos(pos); + } + else + { + ColorI tmp; + mBitmap->getColor(buf_x, buf_y, tmp); + + mPickColor = (ColorF)tmp; + + // Now do onAction() if we are allowed + if (mActionOnMove) + onAction(); + } + } + } + + } + + //render the children + renderChildControls(offset, updateRect); +} + +void GuiColorPickerCtrl::setSelectorPos(const ColorF & color) +{ + if (mBitmap && !mPositionChanged) + { + Point2I resolution = getRoot() ? getRoot()->getExtent() : Point2I(1024, 768); + RectI rect(getGlobalBounds()); + Point2I pos = findColor(color, rect.point, resolution, *mBitmap); + mSetColor = mSetColor.BLACK; + mSelectColor = false; + + setSelectorPos(pos); + } + else + { + mSetColor = color; + mSelectColor = true; + mPositionChanged = true; + } +} + +Point2I GuiColorPickerCtrl::findColor(const ColorF & color, const Point2I& offset, const Point2I& resolution, GBitmap& bmp) +{ + RectI rect; + Point2I ext = getExtent(); + if (mDisplayMode != pDropperBackground) + { + ext.x -= 3; + ext.y -= 2; + rect = RectI(Point2I(1, 1), ext); + } + else + { + rect = RectI(Point2I(0, 0), ext); + } + + Point2I closestPos(-1, -1); + + /* Debugging + char filename[256]; + dSprintf( filename, 256, "%s.%s", "colorPickerTest", "png" ); + + // Open up the file on disk. + FileStream fs; + if ( !fs.open( filename, Torque::FS::File::Write ) ) + Con::errorf( "GuiObjectView::saveAsImage() - Failed to open output file '%s'!", filename ); + else + { + // Write it and close. + bmp.writeBitmap( "png", fs ); + + fs.close(); + } + */ + + ColorI tmp; + U32 buf_x; + U32 buf_y; + ColorF curColor; + F32 val(10000.0f); + F32 closestVal(10000.0f); + bool closestSet = false; + + for (S32 x = rect.point.x; x <= rect.extent.x; x++) + { + for (S32 y = rect.point.y; y <= rect.extent.y; y++) + { + buf_x = offset.x + x + 1; + buf_y = (resolution.y - (offset.y + y + 1)); + if (GFX->getAdapterType() != OpenGL) + buf_y = resolution.y - buf_y; + + //Get the color at that position + bmp.getColor(buf_x, buf_y, tmp); + curColor = (ColorF)tmp; + + //Evaluate how close the color is to our desired color + val = mFabs(color.red - curColor.red) + mFabs(color.green - curColor.green) + mFabs(color.blue - curColor.blue); + + if (!closestSet) + { + closestVal = val; + closestPos.set(x, y); + closestSet = true; + } + else if (val < closestVal) + { + closestVal = val; + closestPos.set(x, y); + } + } + } + + return closestPos; } //-------------------------------------------------------------------------- @@ -539,3 +671,10 @@ DefineConsoleMethod(GuiColorPickerCtrl, updateColor, void, (), , "Forces update { object->updateColor(); } + +DefineEngineMethod(GuiColorPickerCtrl, setSelectorColor, void, (ColorF color), , + "Sets the current position of the selector based on a color.n" + "@param color Color to look for.n") +{ + object->setSelectorPos(color); +} \ No newline at end of file diff --git a/Engine/source/gui/controls/guiColorPicker.h b/Engine/source/gui/controls/guiColorPicker.h index a9421b7f7..202a2b5ca 100644 --- a/Engine/source/gui/controls/guiColorPicker.h +++ b/Engine/source/gui/controls/guiColorPicker.h @@ -98,7 +98,11 @@ class GuiColorPickerCtrl : public GuiControl bool mMouseDown; ///< Mouse button down? bool mActionOnMove; ///< Perform onAction() when position has changed? - + bool mSelectColor; + ColorF mSetColor; + GBitmap* mBitmap; + + Point2I findColor(const ColorF & color, const Point2I& offset, const Point2I& resolution, GBitmap& bmp); S32 mSelectorGap; ///< The half-way "gap" between the selector pos and where the selector is allowed to draw. @@ -113,6 +117,7 @@ class GuiColorPickerCtrl : public GuiControl DECLARE_CATEGORY( "Gui Editor" ); GuiColorPickerCtrl(); + ~GuiColorPickerCtrl(); static void initPersistFields(); void onRender(Point2I offset, const RectI &updateRect); @@ -131,6 +136,7 @@ class GuiColorPickerCtrl : public GuiControl /// @name Selector Functions /// @{ void setSelectorPos(const Point2I &pos); ///< Set new pos (in local coords) + void setSelectorPos(const ColorF & color); Point2I getSelectorPos() {return mSelectorPos;} /// @} diff --git a/Engine/source/gui/controls/guiTextEditCtrl.cpp b/Engine/source/gui/controls/guiTextEditCtrl.cpp index 450be5280..7224f3eb8 100644 --- a/Engine/source/gui/controls/guiTextEditCtrl.cpp +++ b/Engine/source/gui/controls/guiTextEditCtrl.cpp @@ -128,6 +128,8 @@ GuiTextEditCtrl::GuiTextEditCtrl() mActive = true; + mTextValid = true; + mTextOffsetReset = true; mHistoryDirty = false; @@ -1257,15 +1259,21 @@ void GuiTextEditCtrl::onRender(Point2I offset, const RectI &updateRect) //if opaque, fill the update rect with the fill color if ( mProfile->mOpaque ) { - if(isFirstResponder()) - GFX->getDrawUtil()->drawRectFill( ctrlRect, mProfile->mFillColorHL ); - else - GFX->getDrawUtil()->drawRectFill( ctrlRect, mProfile->mFillColor ); + if (!mTextValid) + GFX->getDrawUtil()->drawRectFill(ctrlRect, mProfile->mFillColorNA); + else if (isFirstResponder()) + GFX->getDrawUtil()->drawRectFill(ctrlRect, mProfile->mFillColorHL); + else + GFX->getDrawUtil()->drawRectFill(ctrlRect, mProfile->mFillColor); } //if there's a border, draw the border - if ( mProfile->mBorder ) - renderBorder( ctrlRect, mProfile ); + if (mProfile->mBorder) + { + renderBorder(ctrlRect, mProfile); + if (!mTextValid) + GFX->getDrawUtil()->drawRectFill(ctrlRect, mProfile->mFillColorNA); + } drawText( ctrlRect, isFirstResponder() ); } @@ -1491,6 +1499,24 @@ bool GuiTextEditCtrl::hasText() return (mTextBuffer.length()); } +void GuiTextEditCtrl::invalidText(bool playSound) +{ + mTextValid = false; + + if (playSound) + playDeniedSound(); +} + +void GuiTextEditCtrl::validText() +{ + mTextValid = true; +} + +bool GuiTextEditCtrl::isValidText() +{ + return mTextValid; +} + void GuiTextEditCtrl::playDeniedSound() { if ( mDeniedSound ) @@ -1518,27 +1544,29 @@ void GuiTextEditCtrl::handleCharInput( U16 ascii ) //see if it's a number field if ( mProfile->mNumbersOnly ) { - if ( ascii == '-') - { - //a minus sign only exists at the beginning, and only a single minus sign - if ( mCursorPos != 0 && !isAllTextSelected() ) - { - playDeniedSound(); - return; - } + if (ascii == '-') + { + //a minus sign only exists at the beginning, and only a single minus sign + if (mCursorPos != 0 && !isAllTextSelected()) + { + invalidText(); + return; + } - if ( mInsertOn && ( mTextBuffer.getChar(0) == '-' ) ) - { - playDeniedSound(); - return; - } - } - // BJTODO: This is probably not unicode safe. - else if ( ascii != '.' && (ascii < '0' || ascii > '9') ) - { - playDeniedSound(); - return; - } + if (mInsertOn && (mTextBuffer.getChar(0) == '-')) + { + invalidText(); + return; + } + } + // BJTODO: This is probably not unicode safe. + else if (ascii != '.' && (ascii < '0' || ascii > '9')) + { + invalidText(); + return; + } + else + validText(); } //save the current state @@ -1746,3 +1774,24 @@ DefineEngineMethod( GuiTextEditCtrl, forceValidateText, void, (),, { object->forceValidateText(); } + +DefineEngineMethod(GuiTextEditCtrl, invalidText, void, (bool playSound), (true), + "@brief Trigger the invalid sound and make the box red.nn" + "@param playSound Play the invalid text sound or not.n") +{ + object->invalidText(playSound); +} + + +DefineEngineMethod(GuiTextEditCtrl, validText, void, (), , + "@brief Restores the box to normal color.nn") +{ + object->validText(); +} + +DefineEngineMethod(GuiTextEditCtrl, isValidText, bool, (), , + "@brief Returns if the text is set to valid or not.n" + "@Return true if text is set to valid, false if not.nn") +{ + return object->isValidText(); +} diff --git a/Engine/source/gui/controls/guiTextEditCtrl.h b/Engine/source/gui/controls/guiTextEditCtrl.h index 1821ef182..9d29038f7 100644 --- a/Engine/source/gui/controls/guiTextEditCtrl.h +++ b/Engine/source/gui/controls/guiTextEditCtrl.h @@ -93,6 +93,8 @@ protected: void playDeniedSound(); void execConsoleCallback(); + bool mTextValid; + virtual void handleCharInput( U16 ascii ); S32 findNextWord(); @@ -119,6 +121,10 @@ public: S32 getCursorPos() { return( mCursorPos ); } void setCursorPos( const S32 newPos ); + void invalidText(bool playSound = true); + void validText(); + bool isValidText(); + bool isAllTextSelected(); void selectAllText(); void clearSelectedText(); diff --git a/Engine/source/math/mConsoleFunctions.cpp b/Engine/source/math/mConsoleFunctions.cpp index 1a11fe23e..df380da1b 100644 --- a/Engine/source/math/mConsoleFunctions.cpp +++ b/Engine/source/math/mConsoleFunctions.cpp @@ -103,6 +103,19 @@ DefineConsoleFunction( mRound, S32, ( F32 v ),, return mRound(v); } +DefineConsoleFunction( mRoundColour, F32, ( F32 v, S32 n ), (0), + "Round v to the nth decimal place or the nearest whole number by default." + "@param v Value to roundn" + "@param n Number of decimal places to round to, 0 by defaultn" + "@return The rounded value as a S32." + "@ingroup Math") +{ + if (n <= 0) + return mRound(v); + else + return mRound(v, n); +} + DefineConsoleFunction( mCeil, S32, ( F32 v ),, "Round v up to the nearest integer.\n" "@param v Number to convert to integer." diff --git a/Templates/Empty/game/tools/gui/colorPicker.ed.gui b/Templates/Empty/game/tools/gui/colorPicker.ed.gui index 18dad276e..c203ca52e 100644 --- a/Templates/Empty/game/tools/gui/colorPicker.ed.gui +++ b/Templates/Empty/game/tools/gui/colorPicker.ed.gui @@ -1,289 +1,722 @@ //--- OBJECT WRITE BEGIN --- %guiContent = new GuiColorPickerCtrl(ColorPickerDlg,EditorGuiGroup) { - canSaveDynamicFields = "0"; - isContainer = "1"; - Profile = "ToolsGuiDefaultProfile"; - HorizSizing = "right"; - VertSizing = "bottom"; + displayMode = "Dropper"; // this makes the background visible + actionOnMove = "1"; position = "0 0"; - Extent = "800 600"; - MinExtent = "8 2"; - canSave = "1"; - Visible = "1"; + extent = "1024 768"; + minExtent = "8 2"; + horizSizing = "right"; + vertSizing = "bottom"; + profile = "ToolsGuiDefaultProfile"; + visible = "1"; + active = "1"; + Clickable = "1"; + AffectChildren = "1"; + tooltipProfile = "GuiToolTipProfile"; hovertime = "1000"; - DisplayMode = "Dropper"; // this makes the background visible - ActionOnMove = "1"; + isContainer = "1"; + canSave = "1"; + canSaveDynamicFields = "0"; new GuiWindowCtrl(GuiPickerDlg) { - canSaveDynamicFields = "0"; - isContainer = "1"; - Profile = "ToolsGuiWindowProfile"; - HorizSizing = "windowRelative"; - VertSizing = "windowRelative"; - position = "170 100"; - Extent = "348 347"; - MinExtent = "8 2"; - canSave = "1"; - Visible = "1"; - hovertime = "1000"; text = "Color Picker"; - maxLength = "255"; resizeWidth = "0"; resizeHeight = "0"; canMove = "1"; canClose = "1"; canMinimize = "0"; canMaximize = "0"; - minSize = "50 50"; + canCollapse = "0"; closeCommand = "DoColorPickerCancelCallback(); ColorPickerDlg.getRoot().popDialog(ColorPickerDlg);"; - + position = "170 100"; + extent = "439 317"; + minExtent = "8 2"; + horizSizing = "windowRelative"; + vertSizing = "windowRelative"; + profile = "ToolsGuiWindowProfile"; + visible = "1"; + active = "1"; + Clickable = "1"; + AffectChildren = "1"; + tooltipProfile = "GuiToolTipProfile"; + hovertime = "1000"; + isContainer = "1"; + canSave = "1"; + canSaveDynamicFields = "0"; + new GuiBitmapBorderCtrl(){ // color blend - Profile = "ToolsGuiGroupBorderProfile"; position = "3 24"; - Extent = "255 258"; + extent = "255 258"; + minExtent = "8 2"; + horizSizing = "right"; + vertSizing = "bottom"; + profile = "ToolsGuiGroupBorderProfile"; + visible = "1"; + active = "1"; + Clickable = "1"; + AffectChildren = "1"; + tooltipProfile = "GuiToolTipProfile"; + hovertime = "1000"; + isContainer = "1"; + canSave = "1"; + canSaveDynamicFields = "0"; + + new GuiColorPickerCtrl(ColorBlendSelect) { + baseColor = "1 0 0 1"; + pickColor = "0 0 0 1"; + selectorGap = "1"; + displayMode = "BlendColor"; + actionOnMove = "1"; + position = "1 0"; + extent = "255 258"; + minExtent = "8 2"; + horizSizing = "right"; + vertSizing = "bottom"; + profile = "GuiDefaultProfile"; + visible = "1"; + active = "1"; + command = "updateRGBValues(1);"; + Clickable = "1"; + AffectChildren = "1"; + tooltipProfile = "GuiToolTipProfile"; + hovertime = "1000"; + isContainer = "0"; + canSave = "1"; + canSaveDynamicFields = "0"; + }; }; new GuiBitmapBorderCtrl(){ // Hue - Profile = "ToolsGuiGroupBorderProfile"; position = "263 23"; - Extent = "25 261"; - }; - new GuiBitmapBorderCtrl(){ // new old color - Profile = "ToolsGuiGroupBorderProfile"; - position = "292 37"; - Extent = "52 99"; - }; - new GuiBitmapBorderCtrl(){ // rgb - Profile = "ToolsGuiGroupBorderProfile"; - position = "292 209"; - Extent = "52 75"; - }; - new GuiBitmapBorderCtrl(){ // alpha - Profile = "ToolsGuiGroupBorderProfile"; - position = "3 287"; - Extent = "341 24"; - }; - new GuiColorPickerCtrl(ColorBlendSelect) { - canSaveDynamicFields = "0"; - isContainer = "0"; - Profile = "ToolsGuiDefaultProfile"; - HorizSizing = "right"; - VertSizing = "bottom"; - position = "3 24"; - Extent = "255 258"; - MinExtent = "8 2"; - canSave = "1"; - Visible = "1"; - command = "updateRGBValues(1);"; + extent = "25 261"; + minExtent = "8 2"; + horizSizing = "right"; + vertSizing = "bottom"; + profile = "ToolsGuiGroupBorderProfile"; + visible = "1"; + active = "1"; + Clickable = "1"; + AffectChildren = "1"; + tooltipProfile = "GuiToolTipProfile"; hovertime = "1000"; - baseColor = "1 0 0 1"; - PickColor = "0 0 0 1"; - SelectorGap = "1"; - DisplayMode = "BlendColor"; - ActionOnMove = "1"; - }; - new GuiColorPickerCtrl(ColorRangeSelect) { - canSaveDynamicFields = "0"; - isContainer = "0"; - Profile = "ToolsGuiDefaultProfile"; - HorizSizing = "right"; - VertSizing = "bottom"; - position = "264 24"; - Extent = "21 257"; - MinExtent = "8 2"; + isContainer = "1"; canSave = "1"; - Visible = "1"; - Command = "updatePickerBaseColor(1);"; - hovertime = "1000"; - baseColor = "1 0 0 1"; - PickColor = "1 0 0 1"; - SelectorGap = "1"; - DisplayMode = "VertColor"; - ActionOnMove = "1"; - }; - new GuiTextCtrl() { canSaveDynamicFields = "0"; - isContainer = "0"; - Profile = "ToolsGuiTextProfile"; - HorizSizing = "right"; - VertSizing = "bottom"; - position = "298 215"; - Extent = "8 18"; - MinExtent = "8 2"; - canSave = "1"; - Visible = "1"; - hovertime = "1000"; - text = "R"; - maxLength = "255"; - }; - new GuiTextEditCtrl(Channel_R_Val) { // Red Channal - Profile = "ToolsGuiNumericTextEditProfile"; - HorizSizing = "right"; - VertSizing = "bottom"; - position = "307 215"; - Extent = "34 18"; - text = "0"; - maxLength = "4"; - altCommand = "setColorInfo();"; - }; - new GuiTextCtrl() { - canSaveDynamicFields = "0"; - isContainer = "0"; - Profile = "ToolsGuiTextProfile"; - HorizSizing = "right"; - VertSizing = "bottom"; - position = "297 238"; - Extent = "8 18"; - MinExtent = "8 2"; - canSave = "1"; - Visible = "1"; - hovertime = "1000"; - text = "G"; - maxLength = "255"; - }; - new GuiTextEditCtrl(Channel_G_Val) { // Green Channal - Profile = "ToolsGuiNumericTextEditProfile"; - HorizSizing = "right"; - VertSizing = "bottom"; - position = "307 238"; - Extent = "34 18"; - text = "0"; - maxLength = "4"; - altCommand = "setColorInfo();"; - }; - new GuiTextCtrl() { - canSaveDynamicFields = "0"; - isContainer = "0"; - Profile = "ToolsGuiTextProfile"; - HorizSizing = "right"; - VertSizing = "bottom"; - position = "298 261"; - Extent = "8 18"; - MinExtent = "8 2"; - canSave = "1"; - Visible = "1"; - hovertime = "1000"; - text = "B"; - maxLength = "255"; - }; - new GuiTextEditCtrl(Channel_B_Val) { // Blue Channal - Profile = "ToolsGuiNumericTextEditProfile"; - HorizSizing = "right"; - VertSizing = "bottom"; - position = "307 261"; - Extent = "34 18"; - text = "0"; - maxLength = "4"; - altCommand = "setColorInfo();"; - }; - - - new GuiControl() { - class = "AggregateControl"; - position = "2 290"; - Extent = "341 18"; - - new GuiTextCtrl() { - canSaveDynamicFields = "0"; - isContainer = "0"; - Profile = "ToolsGuiTextProfile"; - HorizSizing = "right"; - VertSizing = "bottom"; - position = "267 0"; - Extent = "29 18"; - MinExtent = "8 2"; - canSave = "1"; - Visible = "1"; + + new GuiColorPickerCtrl(ColorRangeSelect) { + baseColor = "1 0 0 1"; + pickColor = "1 0 0 1"; + selectorGap = "1"; + displayMode = "VertColor"; + actionOnMove = "1"; + position = "1 1"; + extent = "21 257"; + minExtent = "8 2"; + horizSizing = "right"; + vertSizing = "bottom"; + profile = "GuiDefaultProfile"; + visible = "1"; + active = "1"; + command = "updatePickerBaseColor(1);"; + Clickable = "1"; + AffectChildren = "1"; + tooltipProfile = "GuiToolTipProfile"; hovertime = "1000"; - text = "Alpha"; - maxLength = "255"; - }; - new GuiSliderCtrl(ColorAlphaSelect) { - internalName = "slider"; - canSaveDynamicFields = "0"; isContainer = "0"; - Profile = "ToolsGuiSliderProfile"; - HorizSizing = "right"; - VertSizing = "bottom"; - position = "5 3"; - Extent = "251 13"; - MinExtent = "8 2"; canSave = "1"; - Visible = "1"; - altCommand = "$ThisControl.getParent().updateFromChild($ThisControl); updateColorPickerAlpha( $ThisControl.getValue() );"; - hovertime = "1000"; - range = "0 1"; - ticks = "0"; - value = "1"; - }; - new GuiTextEditCtrl(Channel_A_Val) { // Alpha Channal - internalName = "textEdit"; - Profile = "ToolsGuiNumericTextEditProfile"; - HorizSizing = "right"; - VertSizing = "bottom"; - position = "305 0"; - Extent = "34 18"; - text = "0"; - maxLength = "4"; - altCommand = "$ThisControl.getParent().updateFromChild($ThisControl); updateColorPickerAlpha( $ThisControl.getValue() );"; + canSaveDynamicFields = "0"; }; }; - new GuiSwatchButtonCtrl(myColor){ // New Color // - Profile = "ToolsGuiDefaultProfile"; - position = "293 38"; - Extent = "50 50"; - }; - new GuiTextCtrl(){ - Profile = "ToolsGuiDefaultProfile"; + new GuiTextCtrl() { text = "New"; position = "306 22"; - Extent = "26 14"; + extent = "26 14"; + profile = "GuiDefaultProfile"; }; - new GuiSwatchButtonCtrl(oldColor){ // Old Color // - Profile = "ToolsGuiDefaultProfile"; - position = "293 85"; - Extent = "50 50"; + new GuiBitmapBorderCtrl(){ // new old color + position = "292 37"; + extent = "52 99"; + minExtent = "8 2"; + horizSizing = "right"; + vertSizing = "bottom"; + profile = "ToolsGuiGroupBorderProfile"; + visible = "1"; + active = "1"; + Clickable = "1"; + AffectChildren = "1"; + tooltipProfile = "GuiToolTipProfile"; + hovertime = "1000"; + isContainer = "1"; + canSave = "1"; + canSaveDynamicFields = "0"; + + new GuiSwatchButtonCtrl(myColor){ // New Color // + position = "1 1"; + extent = "50 50"; + profile = "GuiDefaultProfile"; + }; + new GuiSwatchButtonCtrl(oldColor){ // Old Color // + position = "1 48"; + extent = "50 50"; + profile = "GuiDefaultProfile"; + }; }; - new GuiTextCtrl(){ - Profile = "ToolsGuiDefaultProfile"; + new GuiTextCtrl() { text = "Old"; position = "310 138"; - Extent = "26 14"; + extent = "26 14"; + profile = "GuiDefaultProfile"; + }; + new GuiBitmapBorderCtrl(){ // Color Text Fields + position = "291 165"; + extent = "141 118"; + minExtent = "8 2"; + horizSizing = "right"; + vertSizing = "bottom"; + profile = "ToolsGuiGroupBorderProfile"; + visible = "1"; + active = "1"; + Clickable = "1"; + AffectChildren = "1"; + tooltipProfile = "GuiToolTipProfile"; + hovertime = "1000"; + isContainer = "1"; + canSave = "1"; + canSaveDynamicFields = "0"; + + new GuiControl() { // rgb + position = "4 0"; + extent = "52 75"; + minExtent = "8 2"; + horizSizing = "right"; + vertSizing = "bottom"; + profile = "ToolsGuiDefaultProfile"; + visible = "1"; + active = "1"; + Clickable = "1"; + AffectChildren = "1"; + tooltipProfile = "GuiToolTipProfile"; + hovertime = "1000"; + isContainer = "1"; + canSave = "1"; + canSaveDynamicFields = "0"; + + new GuiTextCtrl() { + text = "R"; + maxLength = "255"; + margin = "0 0 0 0"; + padding = "0 0 0 0"; + anchorTop = "1"; + anchorBottom = "0"; + anchorLeft = "1"; + anchorRight = "0"; + position = "5 6"; + extent = "8 18"; + minExtent = "8 2"; + horizSizing = "right"; + vertSizing = "bottom"; + profile = "GuiTextProfile"; + visible = "1"; + active = "1"; + Clickable = "1"; + AffectChildren = "1"; + tooltipProfile = "GuiToolTipProfile"; + tooltip = "Red Channel color value."; + hovertime = "1000"; + isContainer = "0"; + canSave = "1"; + canSaveDynamicFields = "0"; + }; + new GuiTextEditCtrl(Channel_R_Val) { // Red Channal + text = "0"; + maxLength = "4"; + position = "14 6"; + extent = "34 18"; + horizSizing = "right"; + vertSizing = "bottom"; + profile = "GuiTextEditProfileNumbersOnly"; + class = "ColorPickerRGBClass"; + tooltipProfile = "GuiToolTipProfile"; + tooltip = "Red Channel color value."; + }; + new GuiTextCtrl() { + text = "G"; + maxLength = "255"; + margin = "0 0 0 0"; + padding = "0 0 0 0"; + anchorTop = "1"; + anchorBottom = "0"; + anchorLeft = "1"; + anchorRight = "0"; + position = "4 29"; + extent = "8 18"; + minExtent = "8 2"; + horizSizing = "right"; + vertSizing = "bottom"; + profile = "GuiTextProfile"; + visible = "1"; + active = "1"; + Clickable = "1"; + AffectChildren = "1"; + tooltipProfile = "GuiToolTipProfile"; + tooltip = "Green Channel color value."; + hovertime = "1000"; + isContainer = "0"; + canSave = "1"; + canSaveDynamicFields = "0"; + }; + new GuiTextEditCtrl(Channel_G_Val) { // Green Channal + text = "0"; + maxLength = "4"; + position = "14 29"; + extent = "34 18"; + horizSizing = "right"; + vertSizing = "bottom"; + profile = "GuiTextEditProfileNumbersOnly"; + class = "ColorPickerRGBClass"; + tooltipProfile = "GuiToolTipProfile"; + tooltip = "Green Channel color value."; + }; + new GuiTextCtrl() { + text = "B"; + maxLength = "255"; + margin = "0 0 0 0"; + padding = "0 0 0 0"; + anchorTop = "1"; + anchorBottom = "0"; + anchorLeft = "1"; + anchorRight = "0"; + position = "5 52"; + extent = "8 18"; + minExtent = "8 2"; + horizSizing = "right"; + vertSizing = "bottom"; + profile = "ToolsGuiTextProfile"; + visible = "1"; + active = "1"; + Clickable = "1"; + AffectChildren = "1"; + tooltipProfile = "GuiToolTipProfile"; + tooltip = "Blue Channel color value."; + hovertime = "1000"; + isContainer = "0"; + canSave = "1"; + canSaveDynamicFields = "0"; + }; + new GuiTextEditCtrl(Channel_B_Val) { // Blue Channal + text = "0"; + maxLength = "4"; + position = "14 52"; + extent = "34 18"; + horizSizing = "right"; + vertSizing = "bottom"; + profile = "GuiTextEditProfileNumbersOnly"; + class = "ColorPickerRGBClass"; + tooltipProfile = "GuiToolTipProfile"; + tooltip = "Blue Channel color value."; + }; + }; + new GuiControl() { + position = "71 0"; + extent = "61 75"; + minExtent = "8 2"; + horizSizing = "right"; + vertSizing = "bottom"; + profile = "ToolsGuiDefaultProfile"; + visible = "1"; + active = "1"; + Clickable = "1"; + AffectChildren = "1"; + tooltipProfile = "GuiToolTipProfile"; + hovertime = "1000"; + isContainer = "1"; + canSave = "1"; + canSaveDynamicFields = "0"; + + new GuiTextCtrl() { + text = "H"; + maxLength = "255"; + margin = "0 0 0 0"; + padding = "0 0 0 0"; + anchorTop = "1"; + anchorBottom = "0"; + anchorLeft = "1"; + anchorRight = "0"; + position = "5 6"; + extent = "8 18"; + minExtent = "8 2"; + horizSizing = "right"; + vertSizing = "bottom"; + profile = "ToolsGuiTextProfile"; + visible = "1"; + active = "1"; + Clickable = "1"; + AffectChildren = "1"; + tooltipProfile = "GuiToolTipProfile"; + tooltip = "Hue Channel color value."; + hovertime = "1000"; + isContainer = "0"; + canSave = "1"; + canSaveDynamicFields = "0"; + }; + new GuiTextEditCtrl(Channel_H_Val) { // Hue Channal + text = "0"; + maxLength = "4"; + position = "14 6"; + extent = "34 18"; + horizSizing = "right"; + vertSizing = "bottom"; + profile = "GuiTextEditProfileNumbersOnly"; + class = "ColorPickerHSBClass"; + tooltipProfile = "GuiToolTipProfile"; + tooltip = "Hue Channel color value."; + }; + new GuiTextCtrl() { + text = "o"; + maxLength = "255"; + margin = "0 0 0 0"; + padding = "0 0 0 0"; + anchorTop = "1"; + anchorBottom = "0"; + anchorLeft = "1"; + anchorRight = "0"; + position = "51 2"; + extent = "8 18"; + minExtent = "8 2"; + horizSizing = "right"; + vertSizing = "bottom"; + profile = "ToolsGuiTextProfile"; + visible = "1"; + active = "1"; + Clickable = "1"; + AffectChildren = "1"; + tooltipProfile = "GuiToolTipProfile"; + hovertime = "1000"; + isContainer = "0"; + canSave = "1"; + canSaveDynamicFields = "0"; + }; + new GuiTextCtrl() { + text = "S"; + maxLength = "255"; + margin = "0 0 0 0"; + padding = "0 0 0 0"; + anchorTop = "1"; + anchorBottom = "0"; + anchorLeft = "1"; + anchorRight = "0"; + position = "4 29"; + extent = "8 18"; + minExtent = "8 2"; + horizSizing = "right"; + vertSizing = "bottom"; + profile = "ToolsGuiTextProfile"; + visible = "1"; + active = "1"; + Clickable = "1"; + AffectChildren = "1"; + tooltipProfile = "GuiToolTipProfile"; + tooltip = "Saturation Channel color value."; + hovertime = "1000"; + isContainer = "0"; + canSave = "1"; + canSaveDynamicFields = "0"; + }; + new GuiTextEditCtrl(Channel_S_Val) { // Saturation Channal + text = "0"; + maxLength = "4"; + position = "14 29"; + extent = "34 18"; + horizSizing = "right"; + vertSizing = "bottom"; + profile = "GuiTextEditProfileNumbersOnly"; + class = "ColorPickerHSBClass"; + tooltipProfile = "GuiToolTipProfile"; + tooltip = "Saturation Channel color value."; + }; + new GuiTextCtrl() { + text = "%"; + maxLength = "255"; + margin = "0 0 0 0"; + padding = "0 0 0 0"; + anchorTop = "1"; + anchorBottom = "0"; + anchorLeft = "1"; + anchorRight = "0"; + position = "51 29"; + extent = "10 18"; + minExtent = "8 2"; + horizSizing = "right"; + vertSizing = "bottom"; + profile = "ToolsGuiTextProfile"; + visible = "1"; + active = "1"; + Clickable = "1"; + AffectChildren = "1"; + tooltipProfile = "GuiToolTipProfile"; + hovertime = "1000"; + isContainer = "0"; + canSave = "1"; + canSaveDynamicFields = "0"; + }; + new GuiTextCtrl() { + text = "B"; + maxLength = "255"; + margin = "0 0 0 0"; + padding = "0 0 0 0"; + anchorTop = "1"; + anchorBottom = "0"; + anchorLeft = "1"; + anchorRight = "0"; + position = "5 52"; + extent = "8 18"; + minExtent = "8 2"; + horizSizing = "right"; + vertSizing = "bottom"; + profile = "ToolsGuiTextProfile"; + visible = "1"; + active = "1"; + Clickable = "1"; + AffectChildren = "1"; + tooltipProfile = "GuiToolTipProfile"; + tooltip = "Brightness Channel color value. Aka value or lightness."; + hovertime = "1000"; + isContainer = "0"; + canSave = "1"; + canSaveDynamicFields = "0"; + }; + new GuiTextEditCtrl(Channel_Br_Val) { // Brightness Channal + text = "0"; + maxLength = "4"; + position = "14 52"; + extent = "34 18"; + horizSizing = "right"; + vertSizing = "bottom"; + profile = "GuiTextEditProfileNumbersOnly"; + class = "ColorPickerHSBClass"; + tooltipProfile = "GuiToolTipProfile"; + tooltip = "Brightness Channel color value. Aka value or lightness."; + }; + new GuiTextCtrl() { + text = "%"; + maxLength = "255"; + margin = "0 0 0 0"; + padding = "0 0 0 0"; + anchorTop = "1"; + anchorBottom = "0"; + anchorLeft = "1"; + anchorRight = "0"; + position = "51 52"; + extent = "10 18"; + minExtent = "8 2"; + horizSizing = "right"; + vertSizing = "bottom"; + profile = "ToolsGuiTextProfile"; + visible = "1"; + active = "1"; + Clickable = "1"; + AffectChildren = "1"; + tooltipProfile = "GuiToolTipProfile"; + hovertime = "1000"; + isContainer = "0"; + canSave = "1"; + canSaveDynamicFields = "0"; + }; + }; + new GuiControl() { + position = "3 87"; + extent = "138 24"; + minExtent = "8 2"; + horizSizing = "right"; + vertSizing = "bottom"; + profile = "ToolsGuiDefaultProfile"; + visible = "1"; + active = "1"; + Clickable = "1"; + AffectChildren = "1"; + tooltipProfile = "GuiToolTipProfile"; + hovertime = "1000"; + isContainer = "1"; + canSave = "1"; + canSaveDynamicFields = "0"; + + new GuiTextCtrl() { + text = "#"; + maxLength = "255"; + margin = "0 0 0 0"; + padding = "0 0 0 0"; + anchorTop = "1"; + anchorBottom = "0"; + anchorLeft = "1"; + anchorRight = "0"; + position = "3 5"; + extent = "8 18"; + minExtent = "8 2"; + horizSizing = "right"; + vertSizing = "bottom"; + profile = "ToolsGuiTextProfile"; + visible = "1"; + active = "1"; + Clickable = "1"; + AffectChildren = "1"; + tooltipProfile = "GuiToolTipProfile"; + tooltip = "Hex representation of Red, Green, Blue Color value."; + hovertime = "1000"; + isContainer = "0"; + canSave = "1"; + canSaveDynamicFields = "0"; + }; + new GuiTextEditCtrl(HexColor_Val) { // Hex Color Field + text = "0"; + maxLength = "6"; + position = "13 5"; + extent = "116 18"; + horizSizing = "right"; + vertSizing = "bottom"; + profile = "GuiTextEditProfile"; + tooltipProfile = "GuiToolTipProfile"; + tooltip = "Hex representation of Red, Green, Blue Color value."; + command = "$thisControl.onKeyDown();"; + }; + }; + }; + new GuiBitmapBorderCtrl(){ // alpha + position = "3 287"; + extent = "429 24"; + minExtent = "8 2"; + horizSizing = "right"; + vertSizing = "bottom"; + profile = "ToolsGuiGroupBorderProfile"; + visible = "1"; + active = "1"; + Clickable = "1"; + AffectChildren = "1"; + tooltipProfile = "GuiToolTipProfile"; + hovertime = "1000"; + isContainer = "1"; + canSave = "1"; + canSaveDynamicFields = "0"; + + new GuiControl() { + position = "-1 3"; + extent = "428 18"; + minExtent = "8 2"; + horizSizing = "width"; + vertSizing = "bottom"; + profile = "ToolsGuiDefaultProfile"; + visible = "1"; + active = "1"; + Clickable = "1"; + AffectChildren = "1"; + tooltipProfile = "GuiToolTipProfile"; + hovertime = "1000"; + isContainer = "1"; + class = "AggregateControl"; + canSave = "1"; + canSaveDynamicFields = "0"; + + new GuiSliderCtrl(ColorAlphaSelect) { + range = "0 1"; + ticks = "0"; + value = "1"; + position = "5 3"; + extent = "341 13"; + minExtent = "8 2"; + horizSizing = "right"; + vertSizing = "bottom"; + profile = "GuiSliderProfile"; + visible = "1"; + active = "1"; + altCommand = "$ThisControl.getParent().updateFromChild($ThisControl); updateColorPickerAlpha( $ThisControl.getValue() );"; + Clickable = "1"; + AffectChildren = "1"; + tooltipProfile = "GuiToolTipProfile"; + hovertime = "1000"; + isContainer = "0"; + internalName = "slider"; + canSave = "1"; + canSaveDynamicFields = "0"; + }; + new GuiTextCtrl() { + text = "Alpha"; + maxLength = "255"; + margin = "0 0 0 0"; + padding = "0 0 0 0"; + anchorTop = "1"; + anchorBottom = "0"; + anchorLeft = "1"; + anchorRight = "0"; + position = "355 0"; + extent = "28 18"; + minExtent = "8 2"; + horizSizing = "right"; + vertSizing = "bottom"; + profile = "ToolsGuiTextProfile"; + visible = "1"; + active = "1"; + Clickable = "1"; + AffectChildren = "1"; + tooltipProfile = "GuiToolTipProfile"; + hovertime = "1000"; + isContainer = "0"; + canSave = "1"; + canSaveDynamicFields = "0"; + }; + new GuiTextEditCtrl(Channel_A_Val) { // Alpha Channal + text = "0"; + maxLength = "4"; + position = "392 0"; + extent = "34 18"; + horizSizing = "right"; + vertSizing = "bottom"; + profile = "GuiTextEditProfileNumbersOnly"; + altCommand = "$ThisControl.getParent().updateFromChild($ThisControl); updateColorPickerAlpha( $ThisControl.getValue() );"; + internalName = "TextEdit"; + }; + }; }; new GuiButtonCtrl() { - canSaveDynamicFields = "0"; - isContainer = "0"; - Profile = "ToolsGuiButtonProfile"; - HorizSizing = "right"; - VertSizing = "bottom"; - position = "144 316"; - Extent = "115 24"; - MinExtent = "8 2"; - canSave = "1"; - Visible = "1"; - Command = "DoColorPickerCallback();"; - hovertime = "1000"; text = "Select"; groupNum = "-1"; buttonType = "PushButton"; useMouseEvents = "0"; + position = "349 37"; + extent = "84 24"; + minExtent = "8 2"; + horizSizing = "right"; + vertSizing = "bottom"; + profile = "ToolsGuiButtonProfile"; + visible = "1"; + active = "1"; + command = "DoColorPickerCallback();"; + Clickable = "1"; + AffectChildren = "1"; + tooltipProfile = "GuiToolTipProfile"; + hovertime = "1000"; + isContainer = "0"; + canSave = "1"; + canSaveDynamicFields = "0"; }; new GuiButtonCtrl() { - canSaveDynamicFields = "0"; - isContainer = "0"; - Profile = "ToolsGuiButtonProfile"; - HorizSizing = "right"; - VertSizing = "bottom"; - position = "268 316"; - Extent = "73 24"; - MinExtent = "8 2"; - canSave = "1"; - Visible = "1"; - Command = "DoColorPickerCancelCallback();"; - hovertime = "1000"; text = "Cancel"; groupNum = "-1"; buttonType = "PushButton"; useMouseEvents = "0"; + position = "349 68"; + extent = "84 24"; + minExtent = "8 2"; + horizSizing = "right"; + vertSizing = "bottom"; + profile = "ToolsGuiButtonProfile"; + visible = "1"; + active = "1"; + command = "DoColorPickerCancelCallback();"; + Clickable = "1"; + AffectChildren = "1"; + tooltipProfile = "GuiToolTipProfile"; + hovertime = "1000"; + isContainer = "0"; + canSave = "1"; + canSaveDynamicFields = "0"; }; }; }; @@ -294,26 +727,6 @@ $ColorPickerCancelCallback = ""; $ColorPickerUpdateCallback = ""; $ColorCallbackType = 1; // ColorI -function ColorFloatToInt( %color ) -{ - %red = getWord( %color, 0 ); - %green = getWord( %color, 1 ); - %blue = getWord( %color, 2 ); - %alpha = getWord( %color, 3 ); - - return mCeil( %red * 255 ) SPC mCeil( %green * 255 ) SPC mCeil( %blue * 255 ) SPC mCeil( %alpha * 255 ); -} - -function ColorIntToFloat( %color ) -{ - %red = getWord( %color, 0 ); - %green = getWord( %color, 1 ); - %blue = getWord( %color, 2 ); - %alpha = getWord( %color, 3 ); - - return ( %red / 255 ) SPC ( %green / 255 ) SPC ( %blue / 255 ) SPC ( %alpha / 255 ); -} - // This function pushes the color picker dialog and returns to a callback the selected value function GetColorI( %currentColor, %callback, %root, %updateCallback, %cancelCallback ) { @@ -333,15 +746,18 @@ function GetColorI( %currentColor, %callback, %root, %updateCallback, %cancelCal ColorAlphaSelect.range = "0 255"; // Set the RGBA displays accordingly - %red = getWord(%currentColor, 0) / 255; - %green = getWord(%currentColor, 1) / 255; - %blue = getWord(%currentColor, 2) / 255; + %red = getWord(%currentColor, 0); + %green = getWord(%currentColor, 1); + %blue = getWord(%currentColor, 2); %alpha = getWord(%currentColor, 3); - // set the initial range blend to correct color, no alpha needed - // this should also set the color blend select right now - ColorRangeSelect.baseColor = %red SPC %green SPC %blue SPC "1.0"; - ColorRangeSelect.updateColor(); + //Set the red green blue text fields + Channel_R_Val.setValue(%red); + Channel_G_Val.setValue(%green); + Channel_B_Val.setValue(%blue); + + //Have the rgb text fields update the rest + Channel_R_Val.onValidate(); if(!isObject(%root)) %root = Canvas; @@ -371,15 +787,18 @@ function GetColorF( %currentColor, %callback, %root, %updateCallback, %cancelCal ColorAlphaSelect.range = "0 1"; // Set the RGBA displays accordingly - %red = getWord(%currentColor, 0); - %green = getWord(%currentColor, 1); - %blue = getWord(%currentColor, 2); - %alpha = getWord(%currentColor, 3); + %red = mRoundColour(getWord(%currentColor, 0), 3); + %green = mRoundColour(getWord(%currentColor, 1), 3); + %blue = mRoundColour(getWord(%currentColor, 2), 3); + %alpha = mRoundColour(getWord(%currentColor, 3), 3); - // set the initial range blend to correct color, no alpha needed - // this should also set the color blend select right now - ColorRangeSelect.baseColor = %red SPC %green SPC %blue SPC "1.0"; - ColorRangeSelect.updateColor(); + //Set the red green blue text fields + Channel_R_Val.setValue(%red); + Channel_G_Val.setValue(%green); + Channel_B_Val.setValue(%blue); + + //Have the rgb text fields update the rest + Channel_R_Val.onValidate(); if(!isObject(%root)) %root = Canvas; @@ -390,6 +809,133 @@ function GetColorF( %currentColor, %callback, %root, %updateCallback, %cancelCal Channel_A_Val.setText( %alpha ); } +function ColorPickerRGBClass::onValidate(%this) +{ + %red = Channel_R_Val.getValue(); + %green = Channel_G_Val.getValue(); + %blue = Channel_B_Val.getValue(); + + //Rest of the fields just do everything with ints so convert + if( $ColorCallbackType != 1 ) + { + %rgb = ColorFloatToInt(%red SPC %green SPC %blue SPC "1.0"); + %red = getWord(%rgb, 0); + %green = getWord(%rgb, 1); + %blue = getWord(%rgb, 2); + } + + //Update all the other color fields + %hsb = ColorRGBToHSB(%red SPC %green SPC %blue); + Channel_H_Val.setValue(getWord(%hsb, 0)); + Channel_S_Val.setValue(getWord(%hsb, 1)); + Channel_Br_Val.setValue(getWord(%hsb, 2)); + + %hex = ColorRGBToHEX(%red SPC %green SPC %blue); + HexColor_Val.setValue(%hex); + HexColor_Val.onKeyDown(); + + //Update everything else with our new color + setColorInfo(); +} + +function ColorPickerHSBClass::onValidate(%this) +{ + %hue = Channel_H_Val.getValue(); + %saturation = Channel_S_Val.getValue(); + %brightness = Channel_Br_Val.getValue(); + + //Update all the other color fields + %rgb = ColorHSBToRGB(%hue SPC %saturation SPC %brightness); + %hex = ColorRGBToHEX(%rgb); + HexColor_Val.setValue(%hex); + HexColor_Val.onKeyDown(); + + //convert to float for rgb if we need to + if( $ColorCallbackType != 1 ) + { + %rgb = ColorIntToFloat(%rgb); + } + %red = getWord(%rgb, 0); + %green = getWord(%rgb, 1); + %blue = getWord(%rgb, 2); + Channel_R_Val.setValue(%red); + Channel_G_Val.setValue(%green); + Channel_B_Val.setValue(%blue); + + //Update everything else with our new color + setColorInfo(); +} + +function HexColor_Val::onKeyDown(%this) +{ + //Get the value + %value = %this.getValue(); + + //It's hex so keep it all uppercase + %value = strupr(%value); + %pos = %this.getCursorPos(); + %this.setValue(%value); + %this.setCursorPos(%pos); + + //Verify that it's a hex value + %value = stripChars(%value, "0123456789ABCDEF"); + if(%value $= "") + { + %this.validText(); + } + else + { + %this.invalidText(false); + } +} + +function HexColor_Val::onValidate(%this) +{ + //if the current text is invalid don't do anyting + if(!%this.isValidText()) + { + %this.invalidText(true); + return; + } + + //Get the current value + %hex = %this.getValue(); + + //Make sure we have 6 characters + while(strlen(%hex) < 6) + { + %hex = "0" @ %hex; + } + %hex = strupr(%hex); + + //Update the value in case there were missing characters + %this.setValue(%hex); + + //Update all the other color fields + %rgb = ColorHEXToRGB(%hex); + %hsb = ColorRGBToHSB(%rgb); + + //convert to float for rgb if we need to + if( $ColorCallbackType != 1 ) + { + %rgb = ColorIntToFloat(%rgb); + } + + %red = getWord(%rgb, 0); + %green = getWord(%rgb, 1); + %blue = getWord(%rgb, 2); + Channel_R_Val.setValue(%red); + Channel_G_Val.setValue(%green); + Channel_B_Val.setValue(%blue); + + Channel_H_Val.setValue(getWord(%hsb, 0)); + Channel_S_Val.setValue(getWord(%hsb, 1)); + Channel_Br_Val.setValue(getWord(%hsb, 2)); + + //Update everything else with our new color + setColorInfo(); +} + // This function is used to update the text controls at the top function setColorInfo() { @@ -398,16 +944,40 @@ function setColorInfo() %blue = Channel_B_Val.getValue(); if( $ColorCallbackType == 1) - { - %red = (%red / 255); - %green = (%green / 255); - %blue = (%blue / 255); - } + %rgb = ColorIntToFloat(%red SPC %green SPC %blue SPC "255"); + else + %rgb = %red SPC %green SPC %blue SPC "1.0"; - $ColorPickerSignal = 1; + $ColorPickerSignal = 0; - ColorBlendSelect.baseColor = %red SPC %green SPC %blue SPC "1.0"; + //Convert color over to hue color + %hsb = ColorRGBToHSB(ColorFloatToInt(%rgb)); + %tempColor = ColorHSBToRGB( getWord(%hsb, 0) SPC 100 SPC 50); + %tempColor = ColorIntToFloat(setWord(%tempColor, 3, 255)); + + //Make sure all the text fields and everything don't update because of the cursors + ColorRangeSelect.update = false; + ColorBlendSelect.update = false; + + //Set values for the hue color picker + ColorRangeSelect.baseColor = %tempColor; + ColorRangeSelect.pickColor = %tempColor; + ColorRangeSelect.updateColor(); + + //Set the cursor for the hue picker + ColorRangeSelect.setSelectorColor(%tempColor); + + //Set the values for the gradient color picker + ColorBlendSelect.baseColor = %tempColor; + ColorBlendSelect.pickColor = %rgb; ColorBlendSelect.updateColor(); + + //Set the cursor for the gradiant color picker + ColorBlendSelect.setSelectorColor(%rgb); + + //Update our current color + %alpha = getWord(myColor.color, 3); + myColor.color = setWord(%rgb, 3, %alpha); } // return mycolor.color @@ -433,11 +1003,17 @@ function DoColorPickerUpdateCallback() // this is called from ColorRangeSelect.updateColor function updatePickerBaseColor( %location ) { + if(!ColorRangeSelect.update) + { + ColorRangeSelect.update = true; + return; + } + if( $ColorPickerSignal && %location ) %pickColor = ColorRangeSelect.baseColor; else %pickColor = ColorRangeSelect.pickColor; - $ColorPickerSignal = 1; + $ColorPickerSignal = 0; %red = getWord(%pickColor, 0); %green = getWord(%pickColor, 1); @@ -451,6 +1027,12 @@ function updatePickerBaseColor( %location ) // this is called from ColorBlendSelect.updateColor function updateRGBValues( %location ) { + if(!ColorBlendSelect.update) + { + ColorBlendSelect.update = true; + return; + } + //update the color based on where it came from if( $ColorPickerSignal && %location ) %pickColor = ColorBlendSelect.baseColor; @@ -465,7 +1047,7 @@ function updateRGBValues( %location ) %alpha = getWord(myColor.color, 3); // set the color! - myColor.color = %red SPC %green SPC %blue SPC %alpha ; + myColor.color = %red SPC %green SPC %blue SPC %alpha; DoColorPickerUpdateCallback(); @@ -488,6 +1070,25 @@ function updateRGBValues( %location ) Channel_G_Val.setValue(%green); Channel_B_Val.setValue(%blue); + //Rest of the fields just do everything with ints so convert + if( $ColorCallbackType != 1 ) + { + %rgb = ColorFloatToInt(%red SPC %green SPC %blue SPC "1.0"); + %red = getWord(%rgb, 0); + %green = getWord(%rgb, 1); + %blue = getWord(%rgb, 2); + } + + //Update all the other color fields + %hsb = ColorRGBToHSB(%red SPC %green SPC %blue); + Channel_H_Val.setValue(getWord(%hsb, 0)); + Channel_S_Val.setValue(getWord(%hsb, 1)); + Channel_Br_Val.setValue(getWord(%hsb, 2)); + + %hex = ColorRGBToHEX(%red SPC %green SPC %blue); + HexColor_Val.setValue(%hex); + HexColor_Val.onKeyDown(); + $ColorPickerSignal = 0; } diff --git a/Templates/Full/game/tools/gui/colorPicker.ed.gui b/Templates/Full/game/tools/gui/colorPicker.ed.gui index 18dad276e..c203ca52e 100644 --- a/Templates/Full/game/tools/gui/colorPicker.ed.gui +++ b/Templates/Full/game/tools/gui/colorPicker.ed.gui @@ -1,289 +1,722 @@ //--- OBJECT WRITE BEGIN --- %guiContent = new GuiColorPickerCtrl(ColorPickerDlg,EditorGuiGroup) { - canSaveDynamicFields = "0"; - isContainer = "1"; - Profile = "ToolsGuiDefaultProfile"; - HorizSizing = "right"; - VertSizing = "bottom"; + displayMode = "Dropper"; // this makes the background visible + actionOnMove = "1"; position = "0 0"; - Extent = "800 600"; - MinExtent = "8 2"; - canSave = "1"; - Visible = "1"; + extent = "1024 768"; + minExtent = "8 2"; + horizSizing = "right"; + vertSizing = "bottom"; + profile = "ToolsGuiDefaultProfile"; + visible = "1"; + active = "1"; + Clickable = "1"; + AffectChildren = "1"; + tooltipProfile = "GuiToolTipProfile"; hovertime = "1000"; - DisplayMode = "Dropper"; // this makes the background visible - ActionOnMove = "1"; + isContainer = "1"; + canSave = "1"; + canSaveDynamicFields = "0"; new GuiWindowCtrl(GuiPickerDlg) { - canSaveDynamicFields = "0"; - isContainer = "1"; - Profile = "ToolsGuiWindowProfile"; - HorizSizing = "windowRelative"; - VertSizing = "windowRelative"; - position = "170 100"; - Extent = "348 347"; - MinExtent = "8 2"; - canSave = "1"; - Visible = "1"; - hovertime = "1000"; text = "Color Picker"; - maxLength = "255"; resizeWidth = "0"; resizeHeight = "0"; canMove = "1"; canClose = "1"; canMinimize = "0"; canMaximize = "0"; - minSize = "50 50"; + canCollapse = "0"; closeCommand = "DoColorPickerCancelCallback(); ColorPickerDlg.getRoot().popDialog(ColorPickerDlg);"; - + position = "170 100"; + extent = "439 317"; + minExtent = "8 2"; + horizSizing = "windowRelative"; + vertSizing = "windowRelative"; + profile = "ToolsGuiWindowProfile"; + visible = "1"; + active = "1"; + Clickable = "1"; + AffectChildren = "1"; + tooltipProfile = "GuiToolTipProfile"; + hovertime = "1000"; + isContainer = "1"; + canSave = "1"; + canSaveDynamicFields = "0"; + new GuiBitmapBorderCtrl(){ // color blend - Profile = "ToolsGuiGroupBorderProfile"; position = "3 24"; - Extent = "255 258"; + extent = "255 258"; + minExtent = "8 2"; + horizSizing = "right"; + vertSizing = "bottom"; + profile = "ToolsGuiGroupBorderProfile"; + visible = "1"; + active = "1"; + Clickable = "1"; + AffectChildren = "1"; + tooltipProfile = "GuiToolTipProfile"; + hovertime = "1000"; + isContainer = "1"; + canSave = "1"; + canSaveDynamicFields = "0"; + + new GuiColorPickerCtrl(ColorBlendSelect) { + baseColor = "1 0 0 1"; + pickColor = "0 0 0 1"; + selectorGap = "1"; + displayMode = "BlendColor"; + actionOnMove = "1"; + position = "1 0"; + extent = "255 258"; + minExtent = "8 2"; + horizSizing = "right"; + vertSizing = "bottom"; + profile = "GuiDefaultProfile"; + visible = "1"; + active = "1"; + command = "updateRGBValues(1);"; + Clickable = "1"; + AffectChildren = "1"; + tooltipProfile = "GuiToolTipProfile"; + hovertime = "1000"; + isContainer = "0"; + canSave = "1"; + canSaveDynamicFields = "0"; + }; }; new GuiBitmapBorderCtrl(){ // Hue - Profile = "ToolsGuiGroupBorderProfile"; position = "263 23"; - Extent = "25 261"; - }; - new GuiBitmapBorderCtrl(){ // new old color - Profile = "ToolsGuiGroupBorderProfile"; - position = "292 37"; - Extent = "52 99"; - }; - new GuiBitmapBorderCtrl(){ // rgb - Profile = "ToolsGuiGroupBorderProfile"; - position = "292 209"; - Extent = "52 75"; - }; - new GuiBitmapBorderCtrl(){ // alpha - Profile = "ToolsGuiGroupBorderProfile"; - position = "3 287"; - Extent = "341 24"; - }; - new GuiColorPickerCtrl(ColorBlendSelect) { - canSaveDynamicFields = "0"; - isContainer = "0"; - Profile = "ToolsGuiDefaultProfile"; - HorizSizing = "right"; - VertSizing = "bottom"; - position = "3 24"; - Extent = "255 258"; - MinExtent = "8 2"; - canSave = "1"; - Visible = "1"; - command = "updateRGBValues(1);"; + extent = "25 261"; + minExtent = "8 2"; + horizSizing = "right"; + vertSizing = "bottom"; + profile = "ToolsGuiGroupBorderProfile"; + visible = "1"; + active = "1"; + Clickable = "1"; + AffectChildren = "1"; + tooltipProfile = "GuiToolTipProfile"; hovertime = "1000"; - baseColor = "1 0 0 1"; - PickColor = "0 0 0 1"; - SelectorGap = "1"; - DisplayMode = "BlendColor"; - ActionOnMove = "1"; - }; - new GuiColorPickerCtrl(ColorRangeSelect) { - canSaveDynamicFields = "0"; - isContainer = "0"; - Profile = "ToolsGuiDefaultProfile"; - HorizSizing = "right"; - VertSizing = "bottom"; - position = "264 24"; - Extent = "21 257"; - MinExtent = "8 2"; + isContainer = "1"; canSave = "1"; - Visible = "1"; - Command = "updatePickerBaseColor(1);"; - hovertime = "1000"; - baseColor = "1 0 0 1"; - PickColor = "1 0 0 1"; - SelectorGap = "1"; - DisplayMode = "VertColor"; - ActionOnMove = "1"; - }; - new GuiTextCtrl() { canSaveDynamicFields = "0"; - isContainer = "0"; - Profile = "ToolsGuiTextProfile"; - HorizSizing = "right"; - VertSizing = "bottom"; - position = "298 215"; - Extent = "8 18"; - MinExtent = "8 2"; - canSave = "1"; - Visible = "1"; - hovertime = "1000"; - text = "R"; - maxLength = "255"; - }; - new GuiTextEditCtrl(Channel_R_Val) { // Red Channal - Profile = "ToolsGuiNumericTextEditProfile"; - HorizSizing = "right"; - VertSizing = "bottom"; - position = "307 215"; - Extent = "34 18"; - text = "0"; - maxLength = "4"; - altCommand = "setColorInfo();"; - }; - new GuiTextCtrl() { - canSaveDynamicFields = "0"; - isContainer = "0"; - Profile = "ToolsGuiTextProfile"; - HorizSizing = "right"; - VertSizing = "bottom"; - position = "297 238"; - Extent = "8 18"; - MinExtent = "8 2"; - canSave = "1"; - Visible = "1"; - hovertime = "1000"; - text = "G"; - maxLength = "255"; - }; - new GuiTextEditCtrl(Channel_G_Val) { // Green Channal - Profile = "ToolsGuiNumericTextEditProfile"; - HorizSizing = "right"; - VertSizing = "bottom"; - position = "307 238"; - Extent = "34 18"; - text = "0"; - maxLength = "4"; - altCommand = "setColorInfo();"; - }; - new GuiTextCtrl() { - canSaveDynamicFields = "0"; - isContainer = "0"; - Profile = "ToolsGuiTextProfile"; - HorizSizing = "right"; - VertSizing = "bottom"; - position = "298 261"; - Extent = "8 18"; - MinExtent = "8 2"; - canSave = "1"; - Visible = "1"; - hovertime = "1000"; - text = "B"; - maxLength = "255"; - }; - new GuiTextEditCtrl(Channel_B_Val) { // Blue Channal - Profile = "ToolsGuiNumericTextEditProfile"; - HorizSizing = "right"; - VertSizing = "bottom"; - position = "307 261"; - Extent = "34 18"; - text = "0"; - maxLength = "4"; - altCommand = "setColorInfo();"; - }; - - - new GuiControl() { - class = "AggregateControl"; - position = "2 290"; - Extent = "341 18"; - - new GuiTextCtrl() { - canSaveDynamicFields = "0"; - isContainer = "0"; - Profile = "ToolsGuiTextProfile"; - HorizSizing = "right"; - VertSizing = "bottom"; - position = "267 0"; - Extent = "29 18"; - MinExtent = "8 2"; - canSave = "1"; - Visible = "1"; + + new GuiColorPickerCtrl(ColorRangeSelect) { + baseColor = "1 0 0 1"; + pickColor = "1 0 0 1"; + selectorGap = "1"; + displayMode = "VertColor"; + actionOnMove = "1"; + position = "1 1"; + extent = "21 257"; + minExtent = "8 2"; + horizSizing = "right"; + vertSizing = "bottom"; + profile = "GuiDefaultProfile"; + visible = "1"; + active = "1"; + command = "updatePickerBaseColor(1);"; + Clickable = "1"; + AffectChildren = "1"; + tooltipProfile = "GuiToolTipProfile"; hovertime = "1000"; - text = "Alpha"; - maxLength = "255"; - }; - new GuiSliderCtrl(ColorAlphaSelect) { - internalName = "slider"; - canSaveDynamicFields = "0"; isContainer = "0"; - Profile = "ToolsGuiSliderProfile"; - HorizSizing = "right"; - VertSizing = "bottom"; - position = "5 3"; - Extent = "251 13"; - MinExtent = "8 2"; canSave = "1"; - Visible = "1"; - altCommand = "$ThisControl.getParent().updateFromChild($ThisControl); updateColorPickerAlpha( $ThisControl.getValue() );"; - hovertime = "1000"; - range = "0 1"; - ticks = "0"; - value = "1"; - }; - new GuiTextEditCtrl(Channel_A_Val) { // Alpha Channal - internalName = "textEdit"; - Profile = "ToolsGuiNumericTextEditProfile"; - HorizSizing = "right"; - VertSizing = "bottom"; - position = "305 0"; - Extent = "34 18"; - text = "0"; - maxLength = "4"; - altCommand = "$ThisControl.getParent().updateFromChild($ThisControl); updateColorPickerAlpha( $ThisControl.getValue() );"; + canSaveDynamicFields = "0"; }; }; - new GuiSwatchButtonCtrl(myColor){ // New Color // - Profile = "ToolsGuiDefaultProfile"; - position = "293 38"; - Extent = "50 50"; - }; - new GuiTextCtrl(){ - Profile = "ToolsGuiDefaultProfile"; + new GuiTextCtrl() { text = "New"; position = "306 22"; - Extent = "26 14"; + extent = "26 14"; + profile = "GuiDefaultProfile"; }; - new GuiSwatchButtonCtrl(oldColor){ // Old Color // - Profile = "ToolsGuiDefaultProfile"; - position = "293 85"; - Extent = "50 50"; + new GuiBitmapBorderCtrl(){ // new old color + position = "292 37"; + extent = "52 99"; + minExtent = "8 2"; + horizSizing = "right"; + vertSizing = "bottom"; + profile = "ToolsGuiGroupBorderProfile"; + visible = "1"; + active = "1"; + Clickable = "1"; + AffectChildren = "1"; + tooltipProfile = "GuiToolTipProfile"; + hovertime = "1000"; + isContainer = "1"; + canSave = "1"; + canSaveDynamicFields = "0"; + + new GuiSwatchButtonCtrl(myColor){ // New Color // + position = "1 1"; + extent = "50 50"; + profile = "GuiDefaultProfile"; + }; + new GuiSwatchButtonCtrl(oldColor){ // Old Color // + position = "1 48"; + extent = "50 50"; + profile = "GuiDefaultProfile"; + }; }; - new GuiTextCtrl(){ - Profile = "ToolsGuiDefaultProfile"; + new GuiTextCtrl() { text = "Old"; position = "310 138"; - Extent = "26 14"; + extent = "26 14"; + profile = "GuiDefaultProfile"; + }; + new GuiBitmapBorderCtrl(){ // Color Text Fields + position = "291 165"; + extent = "141 118"; + minExtent = "8 2"; + horizSizing = "right"; + vertSizing = "bottom"; + profile = "ToolsGuiGroupBorderProfile"; + visible = "1"; + active = "1"; + Clickable = "1"; + AffectChildren = "1"; + tooltipProfile = "GuiToolTipProfile"; + hovertime = "1000"; + isContainer = "1"; + canSave = "1"; + canSaveDynamicFields = "0"; + + new GuiControl() { // rgb + position = "4 0"; + extent = "52 75"; + minExtent = "8 2"; + horizSizing = "right"; + vertSizing = "bottom"; + profile = "ToolsGuiDefaultProfile"; + visible = "1"; + active = "1"; + Clickable = "1"; + AffectChildren = "1"; + tooltipProfile = "GuiToolTipProfile"; + hovertime = "1000"; + isContainer = "1"; + canSave = "1"; + canSaveDynamicFields = "0"; + + new GuiTextCtrl() { + text = "R"; + maxLength = "255"; + margin = "0 0 0 0"; + padding = "0 0 0 0"; + anchorTop = "1"; + anchorBottom = "0"; + anchorLeft = "1"; + anchorRight = "0"; + position = "5 6"; + extent = "8 18"; + minExtent = "8 2"; + horizSizing = "right"; + vertSizing = "bottom"; + profile = "GuiTextProfile"; + visible = "1"; + active = "1"; + Clickable = "1"; + AffectChildren = "1"; + tooltipProfile = "GuiToolTipProfile"; + tooltip = "Red Channel color value."; + hovertime = "1000"; + isContainer = "0"; + canSave = "1"; + canSaveDynamicFields = "0"; + }; + new GuiTextEditCtrl(Channel_R_Val) { // Red Channal + text = "0"; + maxLength = "4"; + position = "14 6"; + extent = "34 18"; + horizSizing = "right"; + vertSizing = "bottom"; + profile = "GuiTextEditProfileNumbersOnly"; + class = "ColorPickerRGBClass"; + tooltipProfile = "GuiToolTipProfile"; + tooltip = "Red Channel color value."; + }; + new GuiTextCtrl() { + text = "G"; + maxLength = "255"; + margin = "0 0 0 0"; + padding = "0 0 0 0"; + anchorTop = "1"; + anchorBottom = "0"; + anchorLeft = "1"; + anchorRight = "0"; + position = "4 29"; + extent = "8 18"; + minExtent = "8 2"; + horizSizing = "right"; + vertSizing = "bottom"; + profile = "GuiTextProfile"; + visible = "1"; + active = "1"; + Clickable = "1"; + AffectChildren = "1"; + tooltipProfile = "GuiToolTipProfile"; + tooltip = "Green Channel color value."; + hovertime = "1000"; + isContainer = "0"; + canSave = "1"; + canSaveDynamicFields = "0"; + }; + new GuiTextEditCtrl(Channel_G_Val) { // Green Channal + text = "0"; + maxLength = "4"; + position = "14 29"; + extent = "34 18"; + horizSizing = "right"; + vertSizing = "bottom"; + profile = "GuiTextEditProfileNumbersOnly"; + class = "ColorPickerRGBClass"; + tooltipProfile = "GuiToolTipProfile"; + tooltip = "Green Channel color value."; + }; + new GuiTextCtrl() { + text = "B"; + maxLength = "255"; + margin = "0 0 0 0"; + padding = "0 0 0 0"; + anchorTop = "1"; + anchorBottom = "0"; + anchorLeft = "1"; + anchorRight = "0"; + position = "5 52"; + extent = "8 18"; + minExtent = "8 2"; + horizSizing = "right"; + vertSizing = "bottom"; + profile = "ToolsGuiTextProfile"; + visible = "1"; + active = "1"; + Clickable = "1"; + AffectChildren = "1"; + tooltipProfile = "GuiToolTipProfile"; + tooltip = "Blue Channel color value."; + hovertime = "1000"; + isContainer = "0"; + canSave = "1"; + canSaveDynamicFields = "0"; + }; + new GuiTextEditCtrl(Channel_B_Val) { // Blue Channal + text = "0"; + maxLength = "4"; + position = "14 52"; + extent = "34 18"; + horizSizing = "right"; + vertSizing = "bottom"; + profile = "GuiTextEditProfileNumbersOnly"; + class = "ColorPickerRGBClass"; + tooltipProfile = "GuiToolTipProfile"; + tooltip = "Blue Channel color value."; + }; + }; + new GuiControl() { + position = "71 0"; + extent = "61 75"; + minExtent = "8 2"; + horizSizing = "right"; + vertSizing = "bottom"; + profile = "ToolsGuiDefaultProfile"; + visible = "1"; + active = "1"; + Clickable = "1"; + AffectChildren = "1"; + tooltipProfile = "GuiToolTipProfile"; + hovertime = "1000"; + isContainer = "1"; + canSave = "1"; + canSaveDynamicFields = "0"; + + new GuiTextCtrl() { + text = "H"; + maxLength = "255"; + margin = "0 0 0 0"; + padding = "0 0 0 0"; + anchorTop = "1"; + anchorBottom = "0"; + anchorLeft = "1"; + anchorRight = "0"; + position = "5 6"; + extent = "8 18"; + minExtent = "8 2"; + horizSizing = "right"; + vertSizing = "bottom"; + profile = "ToolsGuiTextProfile"; + visible = "1"; + active = "1"; + Clickable = "1"; + AffectChildren = "1"; + tooltipProfile = "GuiToolTipProfile"; + tooltip = "Hue Channel color value."; + hovertime = "1000"; + isContainer = "0"; + canSave = "1"; + canSaveDynamicFields = "0"; + }; + new GuiTextEditCtrl(Channel_H_Val) { // Hue Channal + text = "0"; + maxLength = "4"; + position = "14 6"; + extent = "34 18"; + horizSizing = "right"; + vertSizing = "bottom"; + profile = "GuiTextEditProfileNumbersOnly"; + class = "ColorPickerHSBClass"; + tooltipProfile = "GuiToolTipProfile"; + tooltip = "Hue Channel color value."; + }; + new GuiTextCtrl() { + text = "o"; + maxLength = "255"; + margin = "0 0 0 0"; + padding = "0 0 0 0"; + anchorTop = "1"; + anchorBottom = "0"; + anchorLeft = "1"; + anchorRight = "0"; + position = "51 2"; + extent = "8 18"; + minExtent = "8 2"; + horizSizing = "right"; + vertSizing = "bottom"; + profile = "ToolsGuiTextProfile"; + visible = "1"; + active = "1"; + Clickable = "1"; + AffectChildren = "1"; + tooltipProfile = "GuiToolTipProfile"; + hovertime = "1000"; + isContainer = "0"; + canSave = "1"; + canSaveDynamicFields = "0"; + }; + new GuiTextCtrl() { + text = "S"; + maxLength = "255"; + margin = "0 0 0 0"; + padding = "0 0 0 0"; + anchorTop = "1"; + anchorBottom = "0"; + anchorLeft = "1"; + anchorRight = "0"; + position = "4 29"; + extent = "8 18"; + minExtent = "8 2"; + horizSizing = "right"; + vertSizing = "bottom"; + profile = "ToolsGuiTextProfile"; + visible = "1"; + active = "1"; + Clickable = "1"; + AffectChildren = "1"; + tooltipProfile = "GuiToolTipProfile"; + tooltip = "Saturation Channel color value."; + hovertime = "1000"; + isContainer = "0"; + canSave = "1"; + canSaveDynamicFields = "0"; + }; + new GuiTextEditCtrl(Channel_S_Val) { // Saturation Channal + text = "0"; + maxLength = "4"; + position = "14 29"; + extent = "34 18"; + horizSizing = "right"; + vertSizing = "bottom"; + profile = "GuiTextEditProfileNumbersOnly"; + class = "ColorPickerHSBClass"; + tooltipProfile = "GuiToolTipProfile"; + tooltip = "Saturation Channel color value."; + }; + new GuiTextCtrl() { + text = "%"; + maxLength = "255"; + margin = "0 0 0 0"; + padding = "0 0 0 0"; + anchorTop = "1"; + anchorBottom = "0"; + anchorLeft = "1"; + anchorRight = "0"; + position = "51 29"; + extent = "10 18"; + minExtent = "8 2"; + horizSizing = "right"; + vertSizing = "bottom"; + profile = "ToolsGuiTextProfile"; + visible = "1"; + active = "1"; + Clickable = "1"; + AffectChildren = "1"; + tooltipProfile = "GuiToolTipProfile"; + hovertime = "1000"; + isContainer = "0"; + canSave = "1"; + canSaveDynamicFields = "0"; + }; + new GuiTextCtrl() { + text = "B"; + maxLength = "255"; + margin = "0 0 0 0"; + padding = "0 0 0 0"; + anchorTop = "1"; + anchorBottom = "0"; + anchorLeft = "1"; + anchorRight = "0"; + position = "5 52"; + extent = "8 18"; + minExtent = "8 2"; + horizSizing = "right"; + vertSizing = "bottom"; + profile = "ToolsGuiTextProfile"; + visible = "1"; + active = "1"; + Clickable = "1"; + AffectChildren = "1"; + tooltipProfile = "GuiToolTipProfile"; + tooltip = "Brightness Channel color value. Aka value or lightness."; + hovertime = "1000"; + isContainer = "0"; + canSave = "1"; + canSaveDynamicFields = "0"; + }; + new GuiTextEditCtrl(Channel_Br_Val) { // Brightness Channal + text = "0"; + maxLength = "4"; + position = "14 52"; + extent = "34 18"; + horizSizing = "right"; + vertSizing = "bottom"; + profile = "GuiTextEditProfileNumbersOnly"; + class = "ColorPickerHSBClass"; + tooltipProfile = "GuiToolTipProfile"; + tooltip = "Brightness Channel color value. Aka value or lightness."; + }; + new GuiTextCtrl() { + text = "%"; + maxLength = "255"; + margin = "0 0 0 0"; + padding = "0 0 0 0"; + anchorTop = "1"; + anchorBottom = "0"; + anchorLeft = "1"; + anchorRight = "0"; + position = "51 52"; + extent = "10 18"; + minExtent = "8 2"; + horizSizing = "right"; + vertSizing = "bottom"; + profile = "ToolsGuiTextProfile"; + visible = "1"; + active = "1"; + Clickable = "1"; + AffectChildren = "1"; + tooltipProfile = "GuiToolTipProfile"; + hovertime = "1000"; + isContainer = "0"; + canSave = "1"; + canSaveDynamicFields = "0"; + }; + }; + new GuiControl() { + position = "3 87"; + extent = "138 24"; + minExtent = "8 2"; + horizSizing = "right"; + vertSizing = "bottom"; + profile = "ToolsGuiDefaultProfile"; + visible = "1"; + active = "1"; + Clickable = "1"; + AffectChildren = "1"; + tooltipProfile = "GuiToolTipProfile"; + hovertime = "1000"; + isContainer = "1"; + canSave = "1"; + canSaveDynamicFields = "0"; + + new GuiTextCtrl() { + text = "#"; + maxLength = "255"; + margin = "0 0 0 0"; + padding = "0 0 0 0"; + anchorTop = "1"; + anchorBottom = "0"; + anchorLeft = "1"; + anchorRight = "0"; + position = "3 5"; + extent = "8 18"; + minExtent = "8 2"; + horizSizing = "right"; + vertSizing = "bottom"; + profile = "ToolsGuiTextProfile"; + visible = "1"; + active = "1"; + Clickable = "1"; + AffectChildren = "1"; + tooltipProfile = "GuiToolTipProfile"; + tooltip = "Hex representation of Red, Green, Blue Color value."; + hovertime = "1000"; + isContainer = "0"; + canSave = "1"; + canSaveDynamicFields = "0"; + }; + new GuiTextEditCtrl(HexColor_Val) { // Hex Color Field + text = "0"; + maxLength = "6"; + position = "13 5"; + extent = "116 18"; + horizSizing = "right"; + vertSizing = "bottom"; + profile = "GuiTextEditProfile"; + tooltipProfile = "GuiToolTipProfile"; + tooltip = "Hex representation of Red, Green, Blue Color value."; + command = "$thisControl.onKeyDown();"; + }; + }; + }; + new GuiBitmapBorderCtrl(){ // alpha + position = "3 287"; + extent = "429 24"; + minExtent = "8 2"; + horizSizing = "right"; + vertSizing = "bottom"; + profile = "ToolsGuiGroupBorderProfile"; + visible = "1"; + active = "1"; + Clickable = "1"; + AffectChildren = "1"; + tooltipProfile = "GuiToolTipProfile"; + hovertime = "1000"; + isContainer = "1"; + canSave = "1"; + canSaveDynamicFields = "0"; + + new GuiControl() { + position = "-1 3"; + extent = "428 18"; + minExtent = "8 2"; + horizSizing = "width"; + vertSizing = "bottom"; + profile = "ToolsGuiDefaultProfile"; + visible = "1"; + active = "1"; + Clickable = "1"; + AffectChildren = "1"; + tooltipProfile = "GuiToolTipProfile"; + hovertime = "1000"; + isContainer = "1"; + class = "AggregateControl"; + canSave = "1"; + canSaveDynamicFields = "0"; + + new GuiSliderCtrl(ColorAlphaSelect) { + range = "0 1"; + ticks = "0"; + value = "1"; + position = "5 3"; + extent = "341 13"; + minExtent = "8 2"; + horizSizing = "right"; + vertSizing = "bottom"; + profile = "GuiSliderProfile"; + visible = "1"; + active = "1"; + altCommand = "$ThisControl.getParent().updateFromChild($ThisControl); updateColorPickerAlpha( $ThisControl.getValue() );"; + Clickable = "1"; + AffectChildren = "1"; + tooltipProfile = "GuiToolTipProfile"; + hovertime = "1000"; + isContainer = "0"; + internalName = "slider"; + canSave = "1"; + canSaveDynamicFields = "0"; + }; + new GuiTextCtrl() { + text = "Alpha"; + maxLength = "255"; + margin = "0 0 0 0"; + padding = "0 0 0 0"; + anchorTop = "1"; + anchorBottom = "0"; + anchorLeft = "1"; + anchorRight = "0"; + position = "355 0"; + extent = "28 18"; + minExtent = "8 2"; + horizSizing = "right"; + vertSizing = "bottom"; + profile = "ToolsGuiTextProfile"; + visible = "1"; + active = "1"; + Clickable = "1"; + AffectChildren = "1"; + tooltipProfile = "GuiToolTipProfile"; + hovertime = "1000"; + isContainer = "0"; + canSave = "1"; + canSaveDynamicFields = "0"; + }; + new GuiTextEditCtrl(Channel_A_Val) { // Alpha Channal + text = "0"; + maxLength = "4"; + position = "392 0"; + extent = "34 18"; + horizSizing = "right"; + vertSizing = "bottom"; + profile = "GuiTextEditProfileNumbersOnly"; + altCommand = "$ThisControl.getParent().updateFromChild($ThisControl); updateColorPickerAlpha( $ThisControl.getValue() );"; + internalName = "TextEdit"; + }; + }; }; new GuiButtonCtrl() { - canSaveDynamicFields = "0"; - isContainer = "0"; - Profile = "ToolsGuiButtonProfile"; - HorizSizing = "right"; - VertSizing = "bottom"; - position = "144 316"; - Extent = "115 24"; - MinExtent = "8 2"; - canSave = "1"; - Visible = "1"; - Command = "DoColorPickerCallback();"; - hovertime = "1000"; text = "Select"; groupNum = "-1"; buttonType = "PushButton"; useMouseEvents = "0"; + position = "349 37"; + extent = "84 24"; + minExtent = "8 2"; + horizSizing = "right"; + vertSizing = "bottom"; + profile = "ToolsGuiButtonProfile"; + visible = "1"; + active = "1"; + command = "DoColorPickerCallback();"; + Clickable = "1"; + AffectChildren = "1"; + tooltipProfile = "GuiToolTipProfile"; + hovertime = "1000"; + isContainer = "0"; + canSave = "1"; + canSaveDynamicFields = "0"; }; new GuiButtonCtrl() { - canSaveDynamicFields = "0"; - isContainer = "0"; - Profile = "ToolsGuiButtonProfile"; - HorizSizing = "right"; - VertSizing = "bottom"; - position = "268 316"; - Extent = "73 24"; - MinExtent = "8 2"; - canSave = "1"; - Visible = "1"; - Command = "DoColorPickerCancelCallback();"; - hovertime = "1000"; text = "Cancel"; groupNum = "-1"; buttonType = "PushButton"; useMouseEvents = "0"; + position = "349 68"; + extent = "84 24"; + minExtent = "8 2"; + horizSizing = "right"; + vertSizing = "bottom"; + profile = "ToolsGuiButtonProfile"; + visible = "1"; + active = "1"; + command = "DoColorPickerCancelCallback();"; + Clickable = "1"; + AffectChildren = "1"; + tooltipProfile = "GuiToolTipProfile"; + hovertime = "1000"; + isContainer = "0"; + canSave = "1"; + canSaveDynamicFields = "0"; }; }; }; @@ -294,26 +727,6 @@ $ColorPickerCancelCallback = ""; $ColorPickerUpdateCallback = ""; $ColorCallbackType = 1; // ColorI -function ColorFloatToInt( %color ) -{ - %red = getWord( %color, 0 ); - %green = getWord( %color, 1 ); - %blue = getWord( %color, 2 ); - %alpha = getWord( %color, 3 ); - - return mCeil( %red * 255 ) SPC mCeil( %green * 255 ) SPC mCeil( %blue * 255 ) SPC mCeil( %alpha * 255 ); -} - -function ColorIntToFloat( %color ) -{ - %red = getWord( %color, 0 ); - %green = getWord( %color, 1 ); - %blue = getWord( %color, 2 ); - %alpha = getWord( %color, 3 ); - - return ( %red / 255 ) SPC ( %green / 255 ) SPC ( %blue / 255 ) SPC ( %alpha / 255 ); -} - // This function pushes the color picker dialog and returns to a callback the selected value function GetColorI( %currentColor, %callback, %root, %updateCallback, %cancelCallback ) { @@ -333,15 +746,18 @@ function GetColorI( %currentColor, %callback, %root, %updateCallback, %cancelCal ColorAlphaSelect.range = "0 255"; // Set the RGBA displays accordingly - %red = getWord(%currentColor, 0) / 255; - %green = getWord(%currentColor, 1) / 255; - %blue = getWord(%currentColor, 2) / 255; + %red = getWord(%currentColor, 0); + %green = getWord(%currentColor, 1); + %blue = getWord(%currentColor, 2); %alpha = getWord(%currentColor, 3); - // set the initial range blend to correct color, no alpha needed - // this should also set the color blend select right now - ColorRangeSelect.baseColor = %red SPC %green SPC %blue SPC "1.0"; - ColorRangeSelect.updateColor(); + //Set the red green blue text fields + Channel_R_Val.setValue(%red); + Channel_G_Val.setValue(%green); + Channel_B_Val.setValue(%blue); + + //Have the rgb text fields update the rest + Channel_R_Val.onValidate(); if(!isObject(%root)) %root = Canvas; @@ -371,15 +787,18 @@ function GetColorF( %currentColor, %callback, %root, %updateCallback, %cancelCal ColorAlphaSelect.range = "0 1"; // Set the RGBA displays accordingly - %red = getWord(%currentColor, 0); - %green = getWord(%currentColor, 1); - %blue = getWord(%currentColor, 2); - %alpha = getWord(%currentColor, 3); + %red = mRoundColour(getWord(%currentColor, 0), 3); + %green = mRoundColour(getWord(%currentColor, 1), 3); + %blue = mRoundColour(getWord(%currentColor, 2), 3); + %alpha = mRoundColour(getWord(%currentColor, 3), 3); - // set the initial range blend to correct color, no alpha needed - // this should also set the color blend select right now - ColorRangeSelect.baseColor = %red SPC %green SPC %blue SPC "1.0"; - ColorRangeSelect.updateColor(); + //Set the red green blue text fields + Channel_R_Val.setValue(%red); + Channel_G_Val.setValue(%green); + Channel_B_Val.setValue(%blue); + + //Have the rgb text fields update the rest + Channel_R_Val.onValidate(); if(!isObject(%root)) %root = Canvas; @@ -390,6 +809,133 @@ function GetColorF( %currentColor, %callback, %root, %updateCallback, %cancelCal Channel_A_Val.setText( %alpha ); } +function ColorPickerRGBClass::onValidate(%this) +{ + %red = Channel_R_Val.getValue(); + %green = Channel_G_Val.getValue(); + %blue = Channel_B_Val.getValue(); + + //Rest of the fields just do everything with ints so convert + if( $ColorCallbackType != 1 ) + { + %rgb = ColorFloatToInt(%red SPC %green SPC %blue SPC "1.0"); + %red = getWord(%rgb, 0); + %green = getWord(%rgb, 1); + %blue = getWord(%rgb, 2); + } + + //Update all the other color fields + %hsb = ColorRGBToHSB(%red SPC %green SPC %blue); + Channel_H_Val.setValue(getWord(%hsb, 0)); + Channel_S_Val.setValue(getWord(%hsb, 1)); + Channel_Br_Val.setValue(getWord(%hsb, 2)); + + %hex = ColorRGBToHEX(%red SPC %green SPC %blue); + HexColor_Val.setValue(%hex); + HexColor_Val.onKeyDown(); + + //Update everything else with our new color + setColorInfo(); +} + +function ColorPickerHSBClass::onValidate(%this) +{ + %hue = Channel_H_Val.getValue(); + %saturation = Channel_S_Val.getValue(); + %brightness = Channel_Br_Val.getValue(); + + //Update all the other color fields + %rgb = ColorHSBToRGB(%hue SPC %saturation SPC %brightness); + %hex = ColorRGBToHEX(%rgb); + HexColor_Val.setValue(%hex); + HexColor_Val.onKeyDown(); + + //convert to float for rgb if we need to + if( $ColorCallbackType != 1 ) + { + %rgb = ColorIntToFloat(%rgb); + } + %red = getWord(%rgb, 0); + %green = getWord(%rgb, 1); + %blue = getWord(%rgb, 2); + Channel_R_Val.setValue(%red); + Channel_G_Val.setValue(%green); + Channel_B_Val.setValue(%blue); + + //Update everything else with our new color + setColorInfo(); +} + +function HexColor_Val::onKeyDown(%this) +{ + //Get the value + %value = %this.getValue(); + + //It's hex so keep it all uppercase + %value = strupr(%value); + %pos = %this.getCursorPos(); + %this.setValue(%value); + %this.setCursorPos(%pos); + + //Verify that it's a hex value + %value = stripChars(%value, "0123456789ABCDEF"); + if(%value $= "") + { + %this.validText(); + } + else + { + %this.invalidText(false); + } +} + +function HexColor_Val::onValidate(%this) +{ + //if the current text is invalid don't do anyting + if(!%this.isValidText()) + { + %this.invalidText(true); + return; + } + + //Get the current value + %hex = %this.getValue(); + + //Make sure we have 6 characters + while(strlen(%hex) < 6) + { + %hex = "0" @ %hex; + } + %hex = strupr(%hex); + + //Update the value in case there were missing characters + %this.setValue(%hex); + + //Update all the other color fields + %rgb = ColorHEXToRGB(%hex); + %hsb = ColorRGBToHSB(%rgb); + + //convert to float for rgb if we need to + if( $ColorCallbackType != 1 ) + { + %rgb = ColorIntToFloat(%rgb); + } + + %red = getWord(%rgb, 0); + %green = getWord(%rgb, 1); + %blue = getWord(%rgb, 2); + Channel_R_Val.setValue(%red); + Channel_G_Val.setValue(%green); + Channel_B_Val.setValue(%blue); + + Channel_H_Val.setValue(getWord(%hsb, 0)); + Channel_S_Val.setValue(getWord(%hsb, 1)); + Channel_Br_Val.setValue(getWord(%hsb, 2)); + + //Update everything else with our new color + setColorInfo(); +} + // This function is used to update the text controls at the top function setColorInfo() { @@ -398,16 +944,40 @@ function setColorInfo() %blue = Channel_B_Val.getValue(); if( $ColorCallbackType == 1) - { - %red = (%red / 255); - %green = (%green / 255); - %blue = (%blue / 255); - } + %rgb = ColorIntToFloat(%red SPC %green SPC %blue SPC "255"); + else + %rgb = %red SPC %green SPC %blue SPC "1.0"; - $ColorPickerSignal = 1; + $ColorPickerSignal = 0; - ColorBlendSelect.baseColor = %red SPC %green SPC %blue SPC "1.0"; + //Convert color over to hue color + %hsb = ColorRGBToHSB(ColorFloatToInt(%rgb)); + %tempColor = ColorHSBToRGB( getWord(%hsb, 0) SPC 100 SPC 50); + %tempColor = ColorIntToFloat(setWord(%tempColor, 3, 255)); + + //Make sure all the text fields and everything don't update because of the cursors + ColorRangeSelect.update = false; + ColorBlendSelect.update = false; + + //Set values for the hue color picker + ColorRangeSelect.baseColor = %tempColor; + ColorRangeSelect.pickColor = %tempColor; + ColorRangeSelect.updateColor(); + + //Set the cursor for the hue picker + ColorRangeSelect.setSelectorColor(%tempColor); + + //Set the values for the gradient color picker + ColorBlendSelect.baseColor = %tempColor; + ColorBlendSelect.pickColor = %rgb; ColorBlendSelect.updateColor(); + + //Set the cursor for the gradiant color picker + ColorBlendSelect.setSelectorColor(%rgb); + + //Update our current color + %alpha = getWord(myColor.color, 3); + myColor.color = setWord(%rgb, 3, %alpha); } // return mycolor.color @@ -433,11 +1003,17 @@ function DoColorPickerUpdateCallback() // this is called from ColorRangeSelect.updateColor function updatePickerBaseColor( %location ) { + if(!ColorRangeSelect.update) + { + ColorRangeSelect.update = true; + return; + } + if( $ColorPickerSignal && %location ) %pickColor = ColorRangeSelect.baseColor; else %pickColor = ColorRangeSelect.pickColor; - $ColorPickerSignal = 1; + $ColorPickerSignal = 0; %red = getWord(%pickColor, 0); %green = getWord(%pickColor, 1); @@ -451,6 +1027,12 @@ function updatePickerBaseColor( %location ) // this is called from ColorBlendSelect.updateColor function updateRGBValues( %location ) { + if(!ColorBlendSelect.update) + { + ColorBlendSelect.update = true; + return; + } + //update the color based on where it came from if( $ColorPickerSignal && %location ) %pickColor = ColorBlendSelect.baseColor; @@ -465,7 +1047,7 @@ function updateRGBValues( %location ) %alpha = getWord(myColor.color, 3); // set the color! - myColor.color = %red SPC %green SPC %blue SPC %alpha ; + myColor.color = %red SPC %green SPC %blue SPC %alpha; DoColorPickerUpdateCallback(); @@ -488,6 +1070,25 @@ function updateRGBValues( %location ) Channel_G_Val.setValue(%green); Channel_B_Val.setValue(%blue); + //Rest of the fields just do everything with ints so convert + if( $ColorCallbackType != 1 ) + { + %rgb = ColorFloatToInt(%red SPC %green SPC %blue SPC "1.0"); + %red = getWord(%rgb, 0); + %green = getWord(%rgb, 1); + %blue = getWord(%rgb, 2); + } + + //Update all the other color fields + %hsb = ColorRGBToHSB(%red SPC %green SPC %blue); + Channel_H_Val.setValue(getWord(%hsb, 0)); + Channel_S_Val.setValue(getWord(%hsb, 1)); + Channel_Br_Val.setValue(getWord(%hsb, 2)); + + %hex = ColorRGBToHEX(%red SPC %green SPC %blue); + HexColor_Val.setValue(%hex); + HexColor_Val.onKeyDown(); + $ColorPickerSignal = 0; } From 005c3c4b367ed7af19a9bd758bd330ac88a95c38 Mon Sep 17 00:00:00 2001 From: Azaezel Date: Wed, 29 Jul 2015 01:41:09 -0500 Subject: [PATCH 030/109] =?UTF-8?q?From=20Du=C5=A1an=20Joci=C4=87:=20addit?= =?UTF-8?q?ional=20debugdraw=20entries.=20DirectionLine,=20OutlinedText,?= =?UTF-8?q?=20Capsule?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Engine/source/gfx/sim/debugDraw.cpp | 110 +++++++++++++++++++++++++++- Engine/source/gfx/sim/debugDraw.h | 7 +- 2 files changed, 114 insertions(+), 3 deletions(-) diff --git a/Engine/source/gfx/sim/debugDraw.cpp b/Engine/source/gfx/sim/debugDraw.cpp index 68b8c835d..b31a6925e 100644 --- a/Engine/source/gfx/sim/debugDraw.cpp +++ b/Engine/source/gfx/sim/debugDraw.cpp @@ -132,6 +132,11 @@ void DebugDrawer::setupStateBlocks() d.setZReadWrite(false); mRenderZOffSB = GFX->createStateBlock(d); + + d.setCullMode(GFXCullCCW); + d.setZReadWrite(true, false); + d.setBlend(true); + mRenderAlpha = GFX->createStateBlock(d); } void DebugDrawer::render() @@ -158,10 +163,13 @@ void DebugDrawer::render() // Set up the state block... GFXStateBlockRef currSB; - if(p->useZ) + if(p->type==DebugPrim::Capsule){ + currSB = mRenderAlpha; + }else if(p->useZ){ currSB = mRenderZOnSB; - else + }else{ currSB = mRenderZOffSB; + } GFX->setStateBlock( currSB ); Point3F d; @@ -180,6 +188,47 @@ void DebugDrawer::render() PrimBuild::end(); break; + case DebugPrim::DirectionLine: + { + const static F32 ARROW_LENGTH = 0.2f, ARROW_RADIUS = 0.035f, CYLINDER_RADIUS = 0.008f; + Point3F &start = p->a, &end = p->b; + Point3F direction = end - start; + F32 length = direction.len(); + if( length>ARROW_LENGTH ){ + //cylinder with arrow on end + direction *= (1.0f/length); + Point3F baseArrow = end - (direction*ARROW_LENGTH); + GFX->getDrawUtil()->drawCone(currSB->getDesc(), baseArrow, end, ARROW_RADIUS, p->color); + GFX->getDrawUtil()->drawCylinder(currSB->getDesc(), start, baseArrow, CYLINDER_RADIUS, p->color); + }else if( length>0 ){ + //short, so just draw arrow + GFX->getDrawUtil()->drawCone(currSB->getDesc(), start, end, ARROW_RADIUS, p->color); + } + } + break; + case DebugPrim::Capsule: + GFX->getDrawUtil()->drawCapsule(currSB->getDesc(), p->a, p->b.x, p->b.y, p->color); + break; + case DebugPrim::OutlinedText: + { + GFXTransformSaver saver; + Point3F result; + if (MathUtils::mProjectWorldToScreen(p->a, &result, GFX->getViewport(), GFX->getWorldMatrix(), GFX->getProjectionMatrix())) + { + GFX->setClipRect(GFX->getViewport()); + Point2I where = Point2I(result.x, result.y); + + GFX->getDrawUtil()->setBitmapModulation(p->color2); + GFX->getDrawUtil()->drawText(mFont, Point2I(where.x-1, where.y), p->mText); + GFX->getDrawUtil()->drawText(mFont, Point2I(where.x+1, where.y), p->mText); + GFX->getDrawUtil()->drawText(mFont, Point2I(where.x, where.y-1), p->mText); + GFX->getDrawUtil()->drawText(mFont, Point2I(where.x, where.y+1), p->mText); + + GFX->getDrawUtil()->setBitmapModulation(p->color); + GFX->getDrawUtil()->drawText(mFont, where, p->mText); + } + } + break; case DebugPrim::Box: d = p->a - p->b; GFX->getDrawUtil()->drawCube(currSB->getDesc(), d * 0.5, (p->a + p->b) * 0.5, p->color); @@ -262,6 +311,63 @@ void DebugDrawer::drawLine(const Point3F &a, const Point3F &b, const ColorF &col mHead = n; } +void DebugDrawer::drawCapsule(const Point3F &a, const F32 &radius, const F32 &height, const ColorF &color) +{ + if(isFrozen || !isDrawing) + return; + + DebugPrim *n = mPrimChunker.alloc(); + + n->useZ = true; + n->dieTime = 0; + n->a = a; + n->b.x = radius; + n->b.y = height; + n->color = color; + n->type = DebugPrim::Capsule; + + n->next = mHead; + mHead = n; + +} + +void DebugDrawer::drawDirectionLine(const Point3F &a, const Point3F &b, const ColorF &color) +{ + if(isFrozen || !isDrawing) + return; + + DebugPrim *n = mPrimChunker.alloc(); + + n->useZ = true; + n->dieTime = 0; + n->a = a; + n->b = b; + n->color = color; + n->type = DebugPrim::DirectionLine; + + n->next = mHead; + mHead = n; +} + +void DebugDrawer::drawOutlinedText(const Point3F& pos, const String& text, const ColorF &color, const ColorF &colorOutline) +{ + if(isFrozen || !isDrawing) + return; + + DebugPrim *n = mPrimChunker.alloc(); + + n->useZ = false; + n->dieTime = 0; + n->a = pos; + n->color = color; + n->color2 = colorOutline; + dStrncpy(n->mText, text.c_str(), 256); + n->type = DebugPrim::OutlinedText; + + n->next = mHead; + mHead = n; +} + void DebugDrawer::drawTri(const Point3F &a, const Point3F &b, const Point3F &c, const ColorF &color) { if(isFrozen || !isDrawing) diff --git a/Engine/source/gfx/sim/debugDraw.h b/Engine/source/gfx/sim/debugDraw.h index b1ab55970..c650417b0 100644 --- a/Engine/source/gfx/sim/debugDraw.h +++ b/Engine/source/gfx/sim/debugDraw.h @@ -161,6 +161,7 @@ private: { /// Color used for this primitive. ColorF color; + ColorF color2; /// Points used to store positional data. Exact semantics determined by type. Point3F a, b, c; @@ -168,7 +169,10 @@ private: Tri, Box, Line, - Text + Text, + DirectionLine, + OutlinedText, + Capsule, } type; ///< Type of the primitive. The meanings of a,b,c are determined by this. SimTime dieTime; ///< Time at which we should remove this from the list. @@ -188,6 +192,7 @@ private: GFXStateBlockRef mRenderZOffSB; GFXStateBlockRef mRenderZOnSB; + GFXStateBlockRef mRenderAlpha; Resource mFont; From 5d91cf90b386759f2d2ede4773139786a8b32a6d Mon Sep 17 00:00:00 2001 From: Hein-Pieter van Braam Date: Tue, 7 Jul 2015 21:11:26 +0200 Subject: [PATCH 031/109] Fix running on Linux / Intel Mesa drivers * Explicitly request a GL 3.2 context * Enable 'experimental' extensions on glew (This appears necessary to get glew to work with explictly set GL versions) --- Engine/source/gfx/gl/sdl/gfxGLDevice.sdl.cpp | 4 ++++ Engine/source/gfx/gl/tGL/tGL.cpp | 1 + Engine/source/platformSDL/sdlPlatformGL.cpp | 4 +--- 3 files changed, 6 insertions(+), 3 deletions(-) diff --git a/Engine/source/gfx/gl/sdl/gfxGLDevice.sdl.cpp b/Engine/source/gfx/gl/sdl/gfxGLDevice.sdl.cpp index 10e18deb4..74e1773fc 100644 --- a/Engine/source/gfx/gl/sdl/gfxGLDevice.sdl.cpp +++ b/Engine/source/gfx/gl/sdl/gfxGLDevice.sdl.cpp @@ -83,6 +83,10 @@ void GFXGLDevice::enumerateAdapters( Vector &adapterList ) ); SDL_ClearError(); + SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 3); + SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 2); + SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_CORE); + SDL_GLContext tempContext = SDL_GL_CreateContext( tempWindow ); if( !tempContext ) { diff --git a/Engine/source/gfx/gl/tGL/tGL.cpp b/Engine/source/gfx/gl/tGL/tGL.cpp index 94953b487..8d62a7fd1 100644 --- a/Engine/source/gfx/gl/tGL/tGL.cpp +++ b/Engine/source/gfx/gl/tGL/tGL.cpp @@ -29,6 +29,7 @@ namespace GL { void gglPerformBinds() { + glewExperimental = GL_TRUE; GLenum err = glewInit(); AssertFatal(GLEW_OK == err, avar("Error: %s\n", glewGetErrorString(err)) ); } diff --git a/Engine/source/platformSDL/sdlPlatformGL.cpp b/Engine/source/platformSDL/sdlPlatformGL.cpp index 6562f2c80..b71846d8b 100644 --- a/Engine/source/platformSDL/sdlPlatformGL.cpp +++ b/Engine/source/platformSDL/sdlPlatformGL.cpp @@ -13,18 +13,16 @@ namespace PlatformGL return; inited = true; - const U32 majorOGL = 4; + const U32 majorOGL = 3; const U32 minorOGL = 2; U32 debugFlag = 0; #ifdef TORQUE_DEBUG debugFlag |= SDL_GL_CONTEXT_DEBUG_FLAG; #endif -#if 0 // cause problem with glew, no extension load SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, majorOGL); SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, minorOGL); SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_CORE); -#endif SDL_GL_SetAttribute(SDL_GL_CONTEXT_FLAGS, debugFlag); SDL_ClearError(); From 08622e9601da87b2c663042a06ba404e6bc1577a Mon Sep 17 00:00:00 2001 From: Hein-Pieter van Braam Date: Mon, 20 Jul 2015 15:15:04 +0200 Subject: [PATCH 032/109] Use different extension detection mechanism I got this from looking at the code from the glew utility 'glewinfo' on my system it reported the following for GL_ARB_geometry_shader4: OK [MISSING]. This lead me to believe that there are two different ways of determining extensions. The first 'OK' seems to refer to the entrypoint existing while the second one seems to refer to the extension being available. The difference is this: The first OK comes from : GLEW_ARB_geometry_shader4 the global whereas the second 'MISSING' comes from glewGetExtension("GL_ARB_geometry_shader4"). By replacing the gglHasExtension I got the desired result. We may want to implement some caching if we want to keep using GLEW perhaps? Anyway I suggest this merely as a test, I'm not sure if this is a viable long-term solution. --- Engine/source/gfx/gl/tGL/tGL.h | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Engine/source/gfx/gl/tGL/tGL.h b/Engine/source/gfx/gl/tGL/tGL.h index aa1275ee5..849f49093 100644 --- a/Engine/source/gfx/gl/tGL/tGL.h +++ b/Engine/source/gfx/gl/tGL/tGL.h @@ -24,7 +24,8 @@ #define T_GL_H #include "GL/glew.h" -#define gglHasExtension(EXTENSION) GLEW_##EXTENSION +// Slower but reliably detects extensions +#define gglHasExtension(EXTENSION) glewGetExtension("GL_##EXTENSION") #endif From 320718327f1c77c8ad61b29301dbf949ca63307b Mon Sep 17 00:00:00 2001 From: Hein-Pieter van Braam Date: Mon, 20 Jul 2015 16:27:52 +0200 Subject: [PATCH 033/109] Actually generate a working string for glewGetExtension() I don't know how to 'C' apparently. --- Engine/source/gfx/gl/tGL/tGL.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Engine/source/gfx/gl/tGL/tGL.h b/Engine/source/gfx/gl/tGL/tGL.h index 849f49093..37aefe7f3 100644 --- a/Engine/source/gfx/gl/tGL/tGL.h +++ b/Engine/source/gfx/gl/tGL/tGL.h @@ -25,7 +25,7 @@ #include "GL/glew.h" // Slower but reliably detects extensions -#define gglHasExtension(EXTENSION) glewGetExtension("GL_##EXTENSION") +#define gglHasExtension(EXTENSION) glewGetExtension("GL_" # EXTENSION) #endif From eadd77272d857d9478baaf94833210e9af5498ed Mon Sep 17 00:00:00 2001 From: Hein-Pieter van Braam Date: Thu, 30 Jul 2015 22:16:13 +0200 Subject: [PATCH 034/109] Add @Azaezel's exception for Win32 "This chunk causes issues on the win8.1/non-SDL side." --- Engine/source/gfx/gl/tGL/tGL.h | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/Engine/source/gfx/gl/tGL/tGL.h b/Engine/source/gfx/gl/tGL/tGL.h index 37aefe7f3..bb21422b7 100644 --- a/Engine/source/gfx/gl/tGL/tGL.h +++ b/Engine/source/gfx/gl/tGL/tGL.h @@ -24,8 +24,13 @@ #define T_GL_H #include "GL/glew.h" -// Slower but reliably detects extensions +#if defined (TORQUE_OS_WIN) +// This doesn't work on Mesa drivers. +#define gglHasExtension(EXTENSION) GLEW_##EXTENSION +#else +// Slower but reliably detects extensions on Mesa. #define gglHasExtension(EXTENSION) glewGetExtension("GL_" # EXTENSION) +#endif #endif From 4d3db61e941b33cd32253441df9373ce9eaecb66 Mon Sep 17 00:00:00 2001 From: Azaezel Date: Wed, 12 Aug 2015 03:41:49 -0500 Subject: [PATCH 035/109] credit to @MusicMonkey5555 for spotting. asserts for Div/NULLs with mutli-element classes Also includes his magnitude and normalize safe alts --- Engine/source/core/color.h | 1 + Engine/source/math/mPoint2.h | 10 ++++++++++ Engine/source/math/mPoint3.h | 29 ++++++++++++++++++++++++++++- 3 files changed, 39 insertions(+), 1 deletion(-) diff --git a/Engine/source/core/color.h b/Engine/source/core/color.h index b0620fc4f..6378e3579 100644 --- a/Engine/source/core/color.h +++ b/Engine/source/core/color.h @@ -503,6 +503,7 @@ inline ColorI& ColorI::operator*=(const S32 in_mul) inline ColorI& ColorI::operator/=(const S32 in_mul) { + AssertFatal(in_mul != 0.0f, "Error, div by zero..."); red = red / in_mul; green = green / in_mul; blue = blue / in_mul; diff --git a/Engine/source/math/mPoint2.h b/Engine/source/math/mPoint2.h index f814ddbbc..c141400b9 100644 --- a/Engine/source/math/mPoint2.h +++ b/Engine/source/math/mPoint2.h @@ -438,6 +438,7 @@ inline Point2I Point2I::operator/(const Point2I &_vec) const inline Point2I& Point2I::operator/=(const Point2I &_vec) { + AssertFatal(_vec.x != 0 && _vec.y != 0, "Error, div by zero attempted"); x /= _vec.x; y /= _vec.y; return *this; @@ -645,6 +646,7 @@ inline Point2F Point2F::operator/(const Point2F &_vec) const inline Point2F& Point2F::operator/=(const Point2F &_vec) { + AssertFatal(_vec.x != 0 && _vec.y != 0, "Error, div by zero attempted"); x /= _vec.x; y /= _vec.y; return *this; @@ -908,6 +910,14 @@ inline bool mIsNaN( const Point2F &p ) return mIsNaN_F( p.x ) || mIsNaN_F( p.y ); } +/// Return 0 if points are colinear +/// Return positive if p0p1p2 are counter-clockwise +/// Return negative if p0p1p2 are clockwise +inline F64 mCross(const Point2F &p0, const Point2F &p1, const Point2F &pt2) +{ + return (p1.x - p0.x) * (pt2.y - p0.y) - (p1.y - p0.y) * (pt2.x - p0.x); +} + namespace DictHash { diff --git a/Engine/source/math/mPoint3.h b/Engine/source/math/mPoint3.h index 5a85c6cd0..12af91c4b 100644 --- a/Engine/source/math/mPoint3.h +++ b/Engine/source/math/mPoint3.h @@ -233,11 +233,13 @@ class Point3D bool isZero() const; F64 len() const; F64 lenSquared() const; + F64 magnitudeSafe() const; //-------------------------------------- Mathematical mutators public: void neg(); void normalize(); + void normalizeSafe(); void normalize(F64 val); void convolve(const Point3D&); void convolveInverse(const Point3D&); @@ -728,11 +730,13 @@ inline Point3F& Point3F::operator*=(const Point3F &_vec) inline Point3F Point3F::operator/(const Point3F &_vec) const { + AssertFatal(_vec.x != 0.0f && _vec.y != 0.0f && _vec.z != 0.0f, "Error, div by zero attempted"); return Point3F(x / _vec.x, y / _vec.y, z / _vec.z); } inline Point3F& Point3F::operator/=(const Point3F &_vec) { + AssertFatal(_vec.x != 0.0f && _vec.y != 0.0f && _vec.z != 0.0f, "Error, div by zero attempted"); x /= _vec.x; y /= _vec.y; z /= _vec.z; @@ -855,7 +859,8 @@ inline F64 Point3D::lenSquared() const inline F64 Point3D::len() const { - return mSqrtD(x*x + y*y + z*z); + F64 temp = x*x + y*y + z*z; + return (temp > 0.0) ? mSqrtD(temp) : 0.0; } inline void Point3D::normalize() @@ -863,6 +868,28 @@ inline void Point3D::normalize() m_point3D_normalize(*this); } +inline F64 Point3D::magnitudeSafe() const +{ + if( isZero() ) + { + return 0.0; + } + else + { + return len(); + } +} + +inline void Point3D::normalizeSafe() +{ + F64 vmag = magnitudeSafe(); + + if( vmag > POINT_EPSILON ) + { + *this *= F64(1.0 / vmag); + } +} + inline void Point3D::normalize(F64 val) { m_point3D_normalize_f(*this, val); From 27112c468aaa1b1938facf6da679e097595ebbac Mon Sep 17 00:00:00 2001 From: Azaezel Date: Thu, 17 Sep 2015 16:14:49 -0500 Subject: [PATCH 036/109] reversion for https://github.com/GarageGames/Torque3D/commit/a4c09d168029dccac872b5816b21f8298f73f5e9 To be honest, can't remember how I was intending to fix that, but this one's causing it to fail to profile twice in a row, so kill it with fire. --- Engine/source/platform/profiler.cpp | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/Engine/source/platform/profiler.cpp b/Engine/source/platform/profiler.cpp index ca34b4593..fb60453fc 100644 --- a/Engine/source/platform/profiler.cpp +++ b/Engine/source/platform/profiler.cpp @@ -214,12 +214,10 @@ Profiler::~Profiler() void Profiler::reset() { mEnabled = false; // in case we're in a profiler call. - ProfilerData * head = mProfileList; - ProfilerData * curr = NULL; - while ((curr = head) != NULL) + while (mProfileList) { - head = head->mNextProfilerData; - free(curr); + free(mProfileList); + mProfileList = NULL; } for(ProfilerRootData *walk = ProfilerRootData::sRootList; walk; walk = walk->mNextRoot) From 3630f97ab14867887acb196283ee3c09fc73efed Mon Sep 17 00:00:00 2001 From: irei1as Date: Tue, 22 Sep 2015 19:37:42 +0200 Subject: [PATCH 037/109] mQuat.h change to fix QuatF::angleBetween The old version doesn't have that 2.0f in the return that seems to be needed. Also added normalizing inside so it can be used for not-normalized quaternions too. --- Engine/source/math/mQuat.h | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/Engine/source/math/mQuat.h b/Engine/source/math/mQuat.h index 2ea1b94c1..c167e4e18 100644 --- a/Engine/source/math/mQuat.h +++ b/Engine/source/math/mQuat.h @@ -227,8 +227,13 @@ inline F32 QuatF::dot( const QuatF &q ) const inline F32 QuatF::angleBetween( const QuatF & q ) { - // angle between to quaternions - return mAcos(x * q.x + y * q.y + z * q.z + w * q.w); + // angle between two quaternions + QuatF base(x,y,z,w); + base=base.normalize(); + QuatF q_norm=q; + q_norm=q_norm.normalize(); + return 2.0f*mAcos(base.dot(q_norm)); } + #endif // _MQUAT_H_ From f128b451702782edbdd6b04664286b2bae79b6f0 Mon Sep 17 00:00:00 2001 From: rextimmy Date: Fri, 25 Sep 2015 22:40:24 +1000 Subject: [PATCH 038/109] Changed order of fmodex library unload. --- Engine/source/sfx/fmod/sfxFMODDevice.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Engine/source/sfx/fmod/sfxFMODDevice.h b/Engine/source/sfx/fmod/sfxFMODDevice.h index f710faa20..fc15041e9 100644 --- a/Engine/source/sfx/fmod/sfxFMODDevice.h +++ b/Engine/source/sfx/fmod/sfxFMODDevice.h @@ -105,8 +105,8 @@ struct FModFNTable } ~FModFNTable() { - eventDllRef = NULL; dllRef = NULL; + eventDllRef = NULL; delete mutex; } From 1733ecc315197cab5b3f2873a619980d36895c61 Mon Sep 17 00:00:00 2001 From: irei1as Date: Tue, 29 Sep 2015 16:13:01 +0200 Subject: [PATCH 039/109] Updated normalize() It seems normalize() already changes the quaternion so the asignation is not needed. --- Engine/source/math/mQuat.h | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/Engine/source/math/mQuat.h b/Engine/source/math/mQuat.h index c167e4e18..628e298ba 100644 --- a/Engine/source/math/mQuat.h +++ b/Engine/source/math/mQuat.h @@ -229,11 +229,10 @@ inline F32 QuatF::angleBetween( const QuatF & q ) { // angle between two quaternions QuatF base(x,y,z,w); - base=base.normalize(); + base.normalize(); QuatF q_norm=q; - q_norm=q_norm.normalize(); + q_norm.normalize(); return 2.0f*mAcos(base.dot(q_norm)); } - #endif // _MQUAT_H_ From 691a08bb33341500f405fb60fa00c03fe11f9897 Mon Sep 17 00:00:00 2001 From: Azaezel Date: Tue, 6 Oct 2015 15:34:26 -0500 Subject: [PATCH 040/109] Backend correction for the rigid vs rigid collision resolver: First line is to ensure similar behavior to current regarding pushback on the object doing the colliding. Second line applies an impulse to the rigid that was collided with. --- Engine/source/T3D/rigid.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Engine/source/T3D/rigid.cpp b/Engine/source/T3D/rigid.cpp index e2441441f..31f4d411a 100644 --- a/Engine/source/T3D/rigid.cpp +++ b/Engine/source/T3D/rigid.cpp @@ -156,7 +156,7 @@ bool Rigid::resolveCollision(const Point3F& p, const Point3F &normal, Rigid* rig return false; // Compute impulse - F32 d, n = -nv * (1.0f + restitution * rigid->restitution); + F32 d, n = -nv * (2.0f + restitution * rigid->restitution); Point3F a1,b1,c1; mCross(r1,normal,&a1); invWorldInertia.mulV(a1,&b1); @@ -173,7 +173,7 @@ bool Rigid::resolveCollision(const Point3F& p, const Point3F &normal, Rigid* rig applyImpulse(r1,impulse); impulse.neg(); - applyImpulse(r2,impulse); + rigid->applyImpulse(r2, impulse); return true; } From 6d6055c8738c96bc5910ba9be720c2ac0e2abdeb Mon Sep 17 00:00:00 2001 From: blackwc Date: Wed, 7 Oct 2015 03:28:48 -0400 Subject: [PATCH 041/109] fullscreen and windowed mode cli fix --- Templates/Empty/game/core/main.cs | 4 ++-- Templates/Empty/game/core/scripts/client/canvas.cs | 3 +++ Templates/Full/game/core/main.cs | 4 ++-- Templates/Full/game/core/scripts/client/canvas.cs | 3 +++ 4 files changed, 10 insertions(+), 4 deletions(-) diff --git a/Templates/Empty/game/core/main.cs b/Templates/Empty/game/core/main.cs index 0baeb9364..f666c948c 100644 --- a/Templates/Empty/game/core/main.cs +++ b/Templates/Empty/game/core/main.cs @@ -141,11 +141,11 @@ function parseArgs() switch$ (%arg) { case "-fullscreen": - setFullScreen(true); + $cliFullscreen = true; $argUsed[%i]++; case "-windowed": - setFullScreen(false); + $cliFullscreen = false; $argUsed[%i]++; case "-openGL": diff --git a/Templates/Empty/game/core/scripts/client/canvas.cs b/Templates/Empty/game/core/scripts/client/canvas.cs index 69dd6da71..b3a906f46 100644 --- a/Templates/Empty/game/core/scripts/client/canvas.cs +++ b/Templates/Empty/game/core/scripts/client/canvas.cs @@ -32,6 +32,9 @@ function configureCanvas() if ($pref::Video::mode $= "") $pref::Video::mode = "800 600 false 32 60 0"; + if($cliFullscreen !$="") + $pref::Video::mode = setWord($pref::Video::mode, $WORD::FULLSCREEN, $cliFullScreen); + %resX = getWord($pref::Video::mode, $WORD::RES_X); %resY = getWord($pref::Video::mode, $WORD::RES_Y); %fs = getWord($pref::Video::mode, $WORD::FULLSCREEN); diff --git a/Templates/Full/game/core/main.cs b/Templates/Full/game/core/main.cs index 0baeb9364..f666c948c 100644 --- a/Templates/Full/game/core/main.cs +++ b/Templates/Full/game/core/main.cs @@ -141,11 +141,11 @@ function parseArgs() switch$ (%arg) { case "-fullscreen": - setFullScreen(true); + $cliFullscreen = true; $argUsed[%i]++; case "-windowed": - setFullScreen(false); + $cliFullscreen = false; $argUsed[%i]++; case "-openGL": diff --git a/Templates/Full/game/core/scripts/client/canvas.cs b/Templates/Full/game/core/scripts/client/canvas.cs index 69dd6da71..b3a906f46 100644 --- a/Templates/Full/game/core/scripts/client/canvas.cs +++ b/Templates/Full/game/core/scripts/client/canvas.cs @@ -32,6 +32,9 @@ function configureCanvas() if ($pref::Video::mode $= "") $pref::Video::mode = "800 600 false 32 60 0"; + if($cliFullscreen !$="") + $pref::Video::mode = setWord($pref::Video::mode, $WORD::FULLSCREEN, $cliFullScreen); + %resX = getWord($pref::Video::mode, $WORD::RES_X); %resY = getWord($pref::Video::mode, $WORD::RES_Y); %fs = getWord($pref::Video::mode, $WORD::FULLSCREEN); From 5239c2f183787e66393391a9b1d6016d29f8dd65 Mon Sep 17 00:00:00 2001 From: blackwc Date: Wed, 7 Oct 2015 04:56:36 -0400 Subject: [PATCH 042/109] fullscreen and windowed mode cli fix update --- Templates/Empty/game/core/scripts/client/canvas.cs | 4 +++- Templates/Full/game/core/scripts/client/canvas.cs | 4 +++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/Templates/Empty/game/core/scripts/client/canvas.cs b/Templates/Empty/game/core/scripts/client/canvas.cs index b3a906f46..3982b433d 100644 --- a/Templates/Empty/game/core/scripts/client/canvas.cs +++ b/Templates/Empty/game/core/scripts/client/canvas.cs @@ -32,8 +32,10 @@ function configureCanvas() if ($pref::Video::mode $= "") $pref::Video::mode = "800 600 false 32 60 0"; - if($cliFullscreen !$="") + if($cliFullscreen !$="") { $pref::Video::mode = setWord($pref::Video::mode, $WORD::FULLSCREEN, $cliFullScreen); + $cliFullscreen = ""; + } %resX = getWord($pref::Video::mode, $WORD::RES_X); %resY = getWord($pref::Video::mode, $WORD::RES_Y); diff --git a/Templates/Full/game/core/scripts/client/canvas.cs b/Templates/Full/game/core/scripts/client/canvas.cs index b3a906f46..cae9e16b6 100644 --- a/Templates/Full/game/core/scripts/client/canvas.cs +++ b/Templates/Full/game/core/scripts/client/canvas.cs @@ -32,8 +32,10 @@ function configureCanvas() if ($pref::Video::mode $= "") $pref::Video::mode = "800 600 false 32 60 0"; - if($cliFullscreen !$="") + if($cliFullscreen !$= "") { $pref::Video::mode = setWord($pref::Video::mode, $WORD::FULLSCREEN, $cliFullScreen); + $cliFullscreen = ""; + } %resX = getWord($pref::Video::mode, $WORD::RES_X); %resY = getWord($pref::Video::mode, $WORD::RES_Y); From ef5bdc66d33164809f81bde73a31c818a2032457 Mon Sep 17 00:00:00 2001 From: blackwc Date: Sun, 11 Oct 2015 02:34:21 -0400 Subject: [PATCH 043/109] fullscreen and windowed mode cli fix update 2 --- Templates/Empty/game/core/scripts/client/canvas.cs | 14 +++++++------- Templates/Full/game/core/scripts/client/canvas.cs | 14 +++++++------- 2 files changed, 14 insertions(+), 14 deletions(-) diff --git a/Templates/Empty/game/core/scripts/client/canvas.cs b/Templates/Empty/game/core/scripts/client/canvas.cs index 3982b433d..5c1d377c5 100644 --- a/Templates/Empty/game/core/scripts/client/canvas.cs +++ b/Templates/Empty/game/core/scripts/client/canvas.cs @@ -32,11 +32,6 @@ function configureCanvas() if ($pref::Video::mode $= "") $pref::Video::mode = "800 600 false 32 60 0"; - if($cliFullscreen !$="") { - $pref::Video::mode = setWord($pref::Video::mode, $WORD::FULLSCREEN, $cliFullScreen); - $cliFullscreen = ""; - } - %resX = getWord($pref::Video::mode, $WORD::RES_X); %resY = getWord($pref::Video::mode, $WORD::RES_Y); %fs = getWord($pref::Video::mode, $WORD::FULLSCREEN); @@ -44,9 +39,14 @@ function configureCanvas() %rate = getWord($pref::Video::mode, $WORD::REFRESH); %fsaa = getWord($pref::Video::mode, $WORD::AA); - echo("--------------"); - echo("Attempting to set resolution to \"" @ $pref::Video::mode @ "\""); + if($cliFullscreen !$= "") { + %fs = $cliFullscreen; + $cliFullscreen = ""; + } + echo("--------------"); + echo("Attempting to set resolution to \"" @ %resX SPC %resY SPC %fs SPC %bpp SPC %rate SPC %fsaa @ "\""); + %deskRes = getDesktopResolution(); %deskResX = getWord(%deskRes, $WORD::RES_X); %deskResY = getWord(%deskRes, $WORD::RES_Y); diff --git a/Templates/Full/game/core/scripts/client/canvas.cs b/Templates/Full/game/core/scripts/client/canvas.cs index cae9e16b6..5c1d377c5 100644 --- a/Templates/Full/game/core/scripts/client/canvas.cs +++ b/Templates/Full/game/core/scripts/client/canvas.cs @@ -32,11 +32,6 @@ function configureCanvas() if ($pref::Video::mode $= "") $pref::Video::mode = "800 600 false 32 60 0"; - if($cliFullscreen !$= "") { - $pref::Video::mode = setWord($pref::Video::mode, $WORD::FULLSCREEN, $cliFullScreen); - $cliFullscreen = ""; - } - %resX = getWord($pref::Video::mode, $WORD::RES_X); %resY = getWord($pref::Video::mode, $WORD::RES_Y); %fs = getWord($pref::Video::mode, $WORD::FULLSCREEN); @@ -44,9 +39,14 @@ function configureCanvas() %rate = getWord($pref::Video::mode, $WORD::REFRESH); %fsaa = getWord($pref::Video::mode, $WORD::AA); - echo("--------------"); - echo("Attempting to set resolution to \"" @ $pref::Video::mode @ "\""); + if($cliFullscreen !$= "") { + %fs = $cliFullscreen; + $cliFullscreen = ""; + } + echo("--------------"); + echo("Attempting to set resolution to \"" @ %resX SPC %resY SPC %fs SPC %bpp SPC %rate SPC %fsaa @ "\""); + %deskRes = getDesktopResolution(); %deskResX = getWord(%deskRes, $WORD::RES_X); %deskResY = getWord(%deskRes, $WORD::RES_Y); From b1fccc848c6bd45364b6cb7bbe109a117cb179cd Mon Sep 17 00:00:00 2001 From: Azaezel Date: Sat, 7 Nov 2015 09:04:47 -0600 Subject: [PATCH 044/109] corrects ghosted decal datablock lookup flaw --- Engine/source/T3D/decal/decalData.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/Engine/source/T3D/decal/decalData.cpp b/Engine/source/T3D/decal/decalData.cpp index 67436e991..e517d2897 100644 --- a/Engine/source/T3D/decal/decalData.cpp +++ b/Engine/source/T3D/decal/decalData.cpp @@ -284,6 +284,7 @@ void DecalData::unpackData( BitStream *stream ) Parent::unpackData( stream ); stream->read( &lookupName ); + assignName(lookupName); stream->read( &size ); stream->read( &materialName ); _updateMaterial(); From ce2964d2d019f9e0cb2fcb93d290efd10358bc7f Mon Sep 17 00:00:00 2001 From: Azaezel Date: Wed, 11 Nov 2015 13:52:46 -0600 Subject: [PATCH 045/109] diffuse/albedo texture linearization http://http.developer.nvidia.com/GPUGems3/gpugems3_ch24.html --- .../advanced/advancedLightManager.cpp | 2 +- .../source/materials/materialFeatureTypes.cpp | 1 + .../source/materials/materialFeatureTypes.h | 1 + .../source/shaderGen/GLSL/accuFeatureGLSL.cpp | 2 + .../shaderGen/GLSL/shaderFeatureGLSL.cpp | 43 ++-- .../source/shaderGen/GLSL/shaderFeatureGLSL.h | 5 + .../shaderGen/GLSL/shaderGenGLSLInit.cpp | 1 + .../source/shaderGen/HLSL/accuFeatureHLSL.cpp | 2 + .../shaderGen/HLSL/shaderFeatureHLSL.cpp | 42 ++-- .../source/shaderGen/HLSL/shaderFeatureHLSL.h | 5 + .../shaderGen/HLSL/shaderGenHLSLInit.cpp | 1 + .../source/terrain/glsl/terrFeatureGLSL.cpp | 7 + Engine/source/terrain/glsl/terrFeatureGLSL.h | 3 + .../source/terrain/hlsl/terrFeatureHLSL.cpp | 7 + Engine/source/terrain/hlsl/terrFeatureHLSL.h | 4 + Engine/source/util/imposterCapture.cpp | 1 + Templates/Full/game/art/gui/optionsDlg.gui | 216 ++++++++++++++---- .../Full/game/core/scripts/client/defaults.cs | 4 +- .../core/scripts/client/postFx/GammaPostFX.cs | 6 +- .../game/core/scripts/client/postFx/hdr.cs | 15 +- .../game/core/scripts/client/renderManager.cs | 33 +-- .../Full/game/shaders/common/gl/torque.glsl | 33 +++ .../game/shaders/common/postFx/gammaP.hlsl | 9 +- .../game/shaders/common/postFx/gl/gammaP.glsl | 8 + .../common/postFx/hdr/finalPassCombineP.hlsl | 8 + .../postFx/hdr/gl/finalPassCombineP.glsl | 8 + .../Full/game/shaders/common/torque.hlsl | 32 +++ .../shaders/common/water/gl/waterBasicP.glsl | 1 + .../game/shaders/common/water/gl/waterP.glsl | 1 + .../shaders/common/water/waterBasicP.hlsl | 1 + .../game/shaders/common/water/waterP.hlsl | 1 + 31 files changed, 396 insertions(+), 107 deletions(-) diff --git a/Engine/source/lighting/advanced/advancedLightManager.cpp b/Engine/source/lighting/advanced/advancedLightManager.cpp index ef4446a40..d7db83a6b 100644 --- a/Engine/source/lighting/advanced/advancedLightManager.cpp +++ b/Engine/source/lighting/advanced/advancedLightManager.cpp @@ -105,7 +105,7 @@ void AdvancedLightManager::activate( SceneManager *sceneManager ) // we prefer the floating point format if it works. Vector formats; formats.push_back( GFXFormatR16G16B16A16F ); - formats.push_back( GFXFormatR16G16B16A16 ); + //formats.push_back( GFXFormatR16G16B16A16 ); GFXFormat blendTargetFormat = GFX->selectSupportedFormat( &GFXDefaultRenderTargetProfile, formats, true, diff --git a/Engine/source/materials/materialFeatureTypes.cpp b/Engine/source/materials/materialFeatureTypes.cpp index f7a63815b..9cdc57415 100644 --- a/Engine/source/materials/materialFeatureTypes.cpp +++ b/Engine/source/materials/materialFeatureTypes.cpp @@ -46,6 +46,7 @@ ImplementFeatureType( MFT_AlphaTest, MFG_Texture, 7.0f, true ); ImplementFeatureType( MFT_SpecularMap, MFG_Texture, 8.0f, true ); ImplementFeatureType( MFT_NormalMap, MFG_Texture, 9.0f, true ); ImplementFeatureType( MFT_DetailNormalMap, MFG_Texture, 10.0f, true ); +ImplementFeatureType( MFT_Imposter, U32(-1), -1, true ); ImplementFeatureType( MFT_AccuMap, MFG_PreLighting, 2.0f, true ); diff --git a/Engine/source/materials/materialFeatureTypes.h b/Engine/source/materials/materialFeatureTypes.h index 55b52506c..c302045c2 100644 --- a/Engine/source/materials/materialFeatureTypes.h +++ b/Engine/source/materials/materialFeatureTypes.h @@ -94,6 +94,7 @@ DeclareFeatureType( MFT_OverlayMap ); DeclareFeatureType( MFT_DetailMap ); DeclareFeatureType( MFT_DiffuseColor ); DeclareFeatureType( MFT_DetailNormalMap ); +DeclareFeatureType( MFT_Imposter ); DeclareFeatureType( MFT_AccuMap ); DeclareFeatureType( MFT_AccuScale ); diff --git a/Engine/source/shaderGen/GLSL/accuFeatureGLSL.cpp b/Engine/source/shaderGen/GLSL/accuFeatureGLSL.cpp index c663b93ea..04a280e09 100644 --- a/Engine/source/shaderGen/GLSL/accuFeatureGLSL.cpp +++ b/Engine/source/shaderGen/GLSL/accuFeatureGLSL.cpp @@ -144,6 +144,8 @@ void AccuTexFeatGLSL::processPix(Vector &componentList, // get the accu pixel color meta->addStatement( new GenOp( " @ = tex2D(@, @ * @);\r\n", colorAccuDecl, accuMap, inTex, accuScale ) ); + if (!fd.features[MFT_Imposter]) + meta->addStatement(new GenOp(" @ = toLinear(@);\r\n", accuColor, accuColor)); // scale up normals meta->addStatement( new GenOp( " @.xyz = @.xyz * 2.0 - 0.5;\r\n", bumpNorm, bumpNorm ) ); diff --git a/Engine/source/shaderGen/GLSL/shaderFeatureGLSL.cpp b/Engine/source/shaderGen/GLSL/shaderFeatureGLSL.cpp index 7567b5516..eed42dd68 100644 --- a/Engine/source/shaderGen/GLSL/shaderFeatureGLSL.cpp +++ b/Engine/source/shaderGen/GLSL/shaderFeatureGLSL.cpp @@ -828,6 +828,12 @@ Var* ShaderFeatureGLSL::addOutDetailTexCoord( Vector &compon // Base Texture //**************************************************************************** +DiffuseMapFeatGLSL::DiffuseMapFeatGLSL() +: mTorqueDep("shaders/common/gl/torque.glsl") +{ + addDependency(&mTorqueDep); +} + void DiffuseMapFeatGLSL::processVert( Vector &componentList, const MaterialFeatureData &fd ) { @@ -855,20 +861,23 @@ void DiffuseMapFeatGLSL::processPix( Vector &componentList, diffuseMap->sampler = true; diffuseMap->constNum = Var::getTexUnitNum(); // used as texture unit num here + // create sample color var + Var *diffColor = new Var; + diffColor->setType("vec4"); + diffColor->setName("diffuseColor"); + LangElement *colorDecl = new DecOp( diffColor ); + + MultiLine * meta = new MultiLine; + output = meta; + if ( fd.features[MFT_CubeMap] ) { - MultiLine * meta = new MultiLine; - - // create sample color - Var *diffColor = new Var; - diffColor->setType( "vec4" ); - diffColor->setName( "diffuseColor" ); - LangElement *colorDecl = new DecOp( diffColor ); - meta->addStatement( new GenOp( " @ = tex2D(@, @);\r\n", colorDecl, diffuseMap, inTex ) ); + if (!fd.features[MFT_Imposter]) + meta->addStatement( new GenOp(" @ = toLinear(@);\r\n", diffColor, diffColor) ); meta->addStatement( new GenOp( " @;\r\n", assignColor( diffColor, Material::Mul ) ) ); output = meta; @@ -877,8 +886,6 @@ void DiffuseMapFeatGLSL::processPix( Vector &componentList, { // Handle atlased textures // http://www.infinity-universe.com/Infinity/index.php?option=com_content&task=view&id=65&Itemid=47 - MultiLine * meta = new MultiLine; - output = meta; Var *atlasedTex = new Var; atlasedTex->setName("atlasedTexCoord"); @@ -934,11 +941,6 @@ void DiffuseMapFeatGLSL::processPix( Vector &componentList, // For the rest of the feature... inTex = atlasedTex; - // create sample color var - Var *diffColor = new Var; - diffColor->setType("vec4"); - diffColor->setName("diffuseColor"); - // To dump out UV coords... //#define DEBUG_ATLASED_UV_COORDS #ifdef DEBUG_ATLASED_UV_COORDS @@ -954,21 +956,26 @@ void DiffuseMapFeatGLSL::processPix( Vector &componentList, { meta->addStatement(new GenOp( " @ = tex2Dlod(@, float4(@, 0.0, mipLod));\r\n", new DecOp(diffColor), diffuseMap, inTex)); + if (!fd.features[MFT_Imposter]) + meta->addStatement(new GenOp(" @ = toLinear(@);\r\n", diffColor, diffColor)); } else { meta->addStatement(new GenOp( " @ = tex2D(@, @);\r\n", new DecOp(diffColor), diffuseMap, inTex)); + if (!fd.features[MFT_Imposter]) + meta->addStatement(new GenOp(" @ = toLinear(@);\r\n", diffColor, diffColor)); } meta->addStatement(new GenOp( " @;\r\n", assignColor(diffColor, Material::Mul))); } else { - LangElement *statement = new GenOp( "tex2D(@, @)", diffuseMap, inTex ); - output = new GenOp( " @;\r\n", assignColor( statement, Material::Mul ) ); + meta->addStatement(new GenOp("@ = tex2D(@, @);\r\n", colorDecl, diffuseMap, inTex)); + if (!fd.features[MFT_Imposter]) + meta->addStatement(new GenOp(" @ = toLinear(@);\r\n", diffColor, diffColor)); + meta->addStatement(new GenOp(" @;\r\n", assignColor(diffColor, Material::Mul))); } - } ShaderFeature::Resources DiffuseMapFeatGLSL::getResources( const MaterialFeatureData &fd ) diff --git a/Engine/source/shaderGen/GLSL/shaderFeatureGLSL.h b/Engine/source/shaderGen/GLSL/shaderFeatureGLSL.h index a0e831e93..276183d99 100644 --- a/Engine/source/shaderGen/GLSL/shaderFeatureGLSL.h +++ b/Engine/source/shaderGen/GLSL/shaderFeatureGLSL.h @@ -236,7 +236,12 @@ public: /// Base texture class DiffuseMapFeatGLSL : public ShaderFeatureGLSL { + +protected: + + ShaderIncludeDependency mTorqueDep; public: + DiffuseMapFeatGLSL(); virtual void processVert( Vector &componentList, const MaterialFeatureData &fd ); diff --git a/Engine/source/shaderGen/GLSL/shaderGenGLSLInit.cpp b/Engine/source/shaderGen/GLSL/shaderGenGLSLInit.cpp index 3c4065d44..ef15793a5 100644 --- a/Engine/source/shaderGen/GLSL/shaderGenGLSLInit.cpp +++ b/Engine/source/shaderGen/GLSL/shaderGenGLSLInit.cpp @@ -68,6 +68,7 @@ void _initShaderGenGLSL( ShaderGen *shaderGen ) FEATUREMGR->registerFeature( MFT_IsTranslucent, new NamedFeatureGLSL( "Translucent" ) ); FEATUREMGR->registerFeature( MFT_Visibility, new VisibilityFeatGLSL ); FEATUREMGR->registerFeature( MFT_Fog, new FogFeatGLSL ); + FEATUREMGR->registerFeature( MFT_Imposter, new NamedFeatureGLSL( "Imposter" ) ); FEATUREMGR->registerFeature( MFT_NormalsOut, new NormalsOutFeatGLSL ); diff --git a/Engine/source/shaderGen/HLSL/accuFeatureHLSL.cpp b/Engine/source/shaderGen/HLSL/accuFeatureHLSL.cpp index 63161c258..1e90faa12 100644 --- a/Engine/source/shaderGen/HLSL/accuFeatureHLSL.cpp +++ b/Engine/source/shaderGen/HLSL/accuFeatureHLSL.cpp @@ -141,6 +141,8 @@ void AccuTexFeatHLSL::processPix( Vector &componentList, // get the accu pixel color meta->addStatement( new GenOp( " @ = tex2D(@, @ * @);\r\n", colorAccuDecl, accuMap, inTex, accuScale ) ); + if (!fd.features[MFT_Imposter]) + meta->addStatement(new GenOp(" @ = toLinear(@);\r\n", accuColor, accuColor)); // scale up normals meta->addStatement( new GenOp( " @.xyz = @.xyz * 2.0 - 0.5;\r\n", bumpNorm, bumpNorm ) ); diff --git a/Engine/source/shaderGen/HLSL/shaderFeatureHLSL.cpp b/Engine/source/shaderGen/HLSL/shaderFeatureHLSL.cpp index 8f8b5918a..17cf6333c 100644 --- a/Engine/source/shaderGen/HLSL/shaderFeatureHLSL.cpp +++ b/Engine/source/shaderGen/HLSL/shaderFeatureHLSL.cpp @@ -826,6 +826,12 @@ Var* ShaderFeatureHLSL::addOutDetailTexCoord( Vector &compon // Base Texture //**************************************************************************** +DiffuseMapFeatHLSL::DiffuseMapFeatHLSL() +: mTorqueDep("shaders/common/torque.hlsl") +{ + addDependency(&mTorqueDep); +} + void DiffuseMapFeatHLSL::processVert( Vector &componentList, const MaterialFeatureData &fd ) { @@ -853,30 +859,30 @@ void DiffuseMapFeatHLSL::processPix( Vector &componentList, diffuseMap->sampler = true; diffuseMap->constNum = Var::getTexUnitNum(); // used as texture unit num here + // create sample color + Var *diffColor = new Var; + diffColor->setType("float4"); + diffColor->setName("diffuseColor"); + LangElement *colorDecl = new DecOp(diffColor); + + MultiLine * meta = new MultiLine; + output = meta; + if ( fd.features[MFT_CubeMap] ) { - MultiLine * meta = new MultiLine; - - // create sample color - Var *diffColor = new Var; - diffColor->setType( "float4" ); - diffColor->setName( "diffuseColor" ); - LangElement *colorDecl = new DecOp( diffColor ); - meta->addStatement( new GenOp( " @ = tex2D(@, @);\r\n", colorDecl, diffuseMap, inTex ) ); + if (!fd.features[MFT_Imposter]) + meta->addStatement(new GenOp(" @ = toLinear(@);\r\n", diffColor, diffColor)); meta->addStatement( new GenOp( " @;\r\n", assignColor( diffColor, Material::Mul ) ) ); - output = meta; } else if(fd.features[MFT_DiffuseMapAtlas]) { // Handle atlased textures // http://www.infinity-universe.com/Infinity/index.php?option=com_content&task=view&id=65&Itemid=47 - MultiLine * meta = new MultiLine; - output = meta; Var *atlasedTex = new Var; atlasedTex->setName("atlasedTexCoord"); @@ -932,11 +938,6 @@ void DiffuseMapFeatHLSL::processPix( Vector &componentList, // For the rest of the feature... inTex = atlasedTex; - // create sample color var - Var *diffColor = new Var; - diffColor->setType("float4"); - diffColor->setName("diffuseColor"); - // To dump out UV coords... //#define DEBUG_ATLASED_UV_COORDS #ifdef DEBUG_ATLASED_UV_COORDS @@ -958,15 +959,18 @@ void DiffuseMapFeatHLSL::processPix( Vector &componentList, meta->addStatement(new GenOp( " @ = tex2D(@, @);\r\n", new DecOp(diffColor), diffuseMap, inTex)); } + if (!fd.features[MFT_Imposter]) + meta->addStatement(new GenOp(" @ = toLinear(@);\r\n", diffColor, diffColor)); meta->addStatement(new GenOp( " @;\r\n", assignColor(diffColor, Material::Mul))); } else { - LangElement *statement = new GenOp( "tex2D(@, @)", diffuseMap, inTex ); - output = new GenOp( " @;\r\n", assignColor( statement, Material::Mul ) ); + meta->addStatement(new GenOp("@ = tex2D(@, @);\r\n", colorDecl, diffuseMap, inTex)); + if (!fd.features[MFT_Imposter]) + meta->addStatement(new GenOp(" @ = toLinear(@);\r\n", diffColor, diffColor)); + meta->addStatement(new GenOp(" @;\r\n", assignColor(diffColor, Material::Mul))); } - } ShaderFeature::Resources DiffuseMapFeatHLSL::getResources( const MaterialFeatureData &fd ) diff --git a/Engine/source/shaderGen/HLSL/shaderFeatureHLSL.h b/Engine/source/shaderGen/HLSL/shaderFeatureHLSL.h index 3c2fc821e..4f77c175a 100644 --- a/Engine/source/shaderGen/HLSL/shaderFeatureHLSL.h +++ b/Engine/source/shaderGen/HLSL/shaderFeatureHLSL.h @@ -236,7 +236,12 @@ public: /// Base texture class DiffuseMapFeatHLSL : public ShaderFeatureHLSL { +protected: + + ShaderIncludeDependency mTorqueDep; + public: + DiffuseMapFeatHLSL(); virtual void processVert( Vector &componentList, const MaterialFeatureData &fd ); diff --git a/Engine/source/shaderGen/HLSL/shaderGenHLSLInit.cpp b/Engine/source/shaderGen/HLSL/shaderGenHLSLInit.cpp index c754e1b25..a5c6165e0 100644 --- a/Engine/source/shaderGen/HLSL/shaderGenHLSLInit.cpp +++ b/Engine/source/shaderGen/HLSL/shaderGenHLSLInit.cpp @@ -70,6 +70,7 @@ void _initShaderGenHLSL( ShaderGen *shaderGen ) FEATUREMGR->registerFeature( MFT_GlossMap, new NamedFeatureHLSL( "Gloss Map" ) ); FEATUREMGR->registerFeature( MFT_LightbufferMRT, new NamedFeatureHLSL( "Lightbuffer MRT" ) ); FEATUREMGR->registerFeature( MFT_RenderTarget1_Zero, new RenderTargetZeroHLSL( ShaderFeature::RenderTarget1 ) ); + FEATUREMGR->registerFeature( MFT_Imposter, new NamedFeatureHLSL( "Imposter" ) ); FEATUREMGR->registerFeature( MFT_DiffuseMapAtlas, new NamedFeatureHLSL( "Diffuse Map Atlas" ) ); FEATUREMGR->registerFeature( MFT_NormalMapAtlas, new NamedFeatureHLSL( "Normal Map Atlas" ) ); diff --git a/Engine/source/terrain/glsl/terrFeatureGLSL.cpp b/Engine/source/terrain/glsl/terrFeatureGLSL.cpp index ebe052e00..5f32359a8 100644 --- a/Engine/source/terrain/glsl/terrFeatureGLSL.cpp +++ b/Engine/source/terrain/glsl/terrFeatureGLSL.cpp @@ -64,6 +64,12 @@ MODULE_BEGIN( TerrainFeatGLSL ) MODULE_END; +TerrainFeatGLSL::TerrainFeatGLSL() + : mTorqueDep( "shaders/common/gl/torque.glsl" ) + { + addDependency( &mTorqueDep ); + } + Var* TerrainFeatGLSL::_getUniformVar( const char *name, const char *type, ConstantSortPosition csp ) { Var *theVar = (Var*)LangElement::find( name ); @@ -262,6 +268,7 @@ void TerrainBaseMapFeatGLSL::processPix( Vector &componentLis baseColor->setType( "vec4" ); baseColor->setName( "baseColor" ); meta->addStatement( new GenOp( " @ = tex2D( @, @.xy );\r\n", new DecOp( baseColor ), diffuseMap, texCoord ) ); + meta->addStatement(new GenOp(" @ = toLinear(@);\r\n", baseColor, baseColor)); meta->addStatement( new GenOp( " @;\r\n", assignColor( baseColor, Material::Mul ) ) ); output = meta; diff --git a/Engine/source/terrain/glsl/terrFeatureGLSL.h b/Engine/source/terrain/glsl/terrFeatureGLSL.h index 790a8a342..6cb3a3c0c 100644 --- a/Engine/source/terrain/glsl/terrFeatureGLSL.h +++ b/Engine/source/terrain/glsl/terrFeatureGLSL.h @@ -36,7 +36,10 @@ class TerrainFeatGLSL : public ShaderFeatureGLSL { protected: + ShaderIncludeDependency mTorqueDep; +public: + TerrainFeatGLSL(); Var* _getInDetailCoord(Vector &componentList ); Var* _getInMacroCoord(Vector &componentList ); diff --git a/Engine/source/terrain/hlsl/terrFeatureHLSL.cpp b/Engine/source/terrain/hlsl/terrFeatureHLSL.cpp index 7190d7f82..6ef1c2009 100644 --- a/Engine/source/terrain/hlsl/terrFeatureHLSL.cpp +++ b/Engine/source/terrain/hlsl/terrFeatureHLSL.cpp @@ -64,6 +64,12 @@ MODULE_BEGIN( TerrainFeatHLSL ) MODULE_END; +TerrainFeatHLSL::TerrainFeatHLSL() + : mTorqueDep( "shaders/common/torque.hlsl" ) + { + addDependency( &mTorqueDep ); + } + Var* TerrainFeatHLSL::_getUniformVar( const char *name, const char *type, ConstantSortPosition csp ) { Var *theVar = (Var*)LangElement::find( name ); @@ -262,6 +268,7 @@ void TerrainBaseMapFeatHLSL::processPix( Vector &componentLis baseColor->setType( "float4" ); baseColor->setName( "baseColor" ); meta->addStatement( new GenOp( " @ = tex2D( @, @.xy );\r\n", new DecOp( baseColor ), diffuseMap, texCoord ) ); + meta->addStatement(new GenOp(" @ = toLinear(@);\r\n", baseColor, baseColor)); meta->addStatement( new GenOp( " @;\r\n", assignColor( baseColor, Material::Mul ) ) ); output = meta; diff --git a/Engine/source/terrain/hlsl/terrFeatureHLSL.h b/Engine/source/terrain/hlsl/terrFeatureHLSL.h index 85f75a29e..e6297f3b4 100644 --- a/Engine/source/terrain/hlsl/terrFeatureHLSL.h +++ b/Engine/source/terrain/hlsl/terrFeatureHLSL.h @@ -37,6 +37,10 @@ class TerrainFeatHLSL : public ShaderFeatureHLSL { protected: + ShaderIncludeDependency mTorqueDep; + +public: + TerrainFeatHLSL(); Var* _getInDetailCoord(Vector &componentList ); Var* _getInMacroCoord(Vector &componentList ); diff --git a/Engine/source/util/imposterCapture.cpp b/Engine/source/util/imposterCapture.cpp index fb0786390..f6bcba23f 100644 --- a/Engine/source/util/imposterCapture.cpp +++ b/Engine/source/util/imposterCapture.cpp @@ -136,6 +136,7 @@ void ImposterCaptureMaterialHook::_overrideFeatures( ProcessedMaterial *mat, fd.features.addFeature( MFT_NormalsOut ); fd.features.addFeature( MFT_ForwardShading ); + fd.features.addFeature( MFT_Imposter ); } ImposterCaptureMaterialHook* ImposterCaptureMaterialHook::_getOrCreateHook( BaseMatInstance *inMat ) diff --git a/Templates/Full/game/art/gui/optionsDlg.gui b/Templates/Full/game/art/gui/optionsDlg.gui index fcff1cb95..9f12a272a 100644 --- a/Templates/Full/game/art/gui/optionsDlg.gui +++ b/Templates/Full/game/art/gui/optionsDlg.gui @@ -33,7 +33,7 @@ anchorLeft = "1"; anchorRight = "0"; position = "323 232"; - extent = "377 303"; + extent = "377 355"; minExtent = "8 8"; horizSizing = "center"; vertSizing = "center"; @@ -51,7 +51,7 @@ groupNum = "-1"; buttonType = "PushButton"; useMouseEvents = "0"; - position = "306 271"; + position = "304 319"; extent = "60 23"; minExtent = "8 8"; horizSizing = "right"; @@ -179,47 +179,49 @@ canSave = "1"; canSaveDynamicFields = "0"; }; - - new GuiSliderCtrl(OptMouseSensitivity) { - range = "0.02 2"; - ticks = "10"; - value = "0.75"; - isContainer = "0"; - Profile = "GuiSliderProfile"; - HorizSizing = "right"; - VertSizing = "bottom"; - position = "105 182"; - Extent = "244 18"; - MinExtent = "8 2"; - canSave = "1"; - Visible = "1"; - Command = "OptMouseSetSensitivity(OptMouseSensitivity.value);"; - tooltipprofile = "GuiToolTipProfile"; - hovertime = "1000"; - canSaveDynamicFields = "0"; - }; - new GuiTextCtrl() { - text = "Mouse Sensitivity:"; - maxLength = "255"; - Margin = "0 0 0 0"; - Padding = "0 0 0 0"; - AnchorTop = "1"; - AnchorBottom = "0"; - AnchorLeft = "1"; - AnchorRight = "0"; - isContainer = "0"; - Profile = "GuiTextProfile"; - HorizSizing = "right"; - VertSizing = "bottom"; - position = "15 182"; - Extent = "85 18"; - MinExtent = "8 8"; - canSave = "1"; - Visible = "1"; - tooltipprofile = "GuiToolTipProfile"; - hovertime = "1000"; - canSaveDynamicFields = "0"; - }; + new GuiSliderCtrl(OptMouseSensitivity) { + range = "0.02 2"; + ticks = "10"; + snap = "0"; + value = "1"; + position = "105 182"; + extent = "244 18"; + minExtent = "8 2"; + horizSizing = "right"; + vertSizing = "bottom"; + profile = "GuiSliderProfile"; + visible = "1"; + active = "1"; + command = "OptMouseSetSensitivity(OptMouseSensitivity.value);"; + tooltipProfile = "GuiToolTipProfile"; + hovertime = "1000"; + isContainer = "0"; + canSave = "1"; + canSaveDynamicFields = "0"; + }; + new GuiTextCtrl() { + text = "Mouse Sensitivity:"; + maxLength = "255"; + margin = "0 0 0 0"; + padding = "0 0 0 0"; + anchorTop = "1"; + anchorBottom = "0"; + anchorLeft = "1"; + anchorRight = "0"; + position = "15 182"; + extent = "85 18"; + minExtent = "8 8"; + horizSizing = "right"; + vertSizing = "bottom"; + profile = "GuiTextProfile"; + visible = "1"; + active = "1"; + tooltipProfile = "GuiToolTipProfile"; + hovertime = "1000"; + isContainer = "0"; + canSave = "1"; + canSaveDynamicFields = "0"; + }; }; new GuiBitmapBorderCtrl() { position = "9 55"; @@ -601,7 +603,7 @@ }; new GuiBitmapBorderCtrl() { position = "9 55"; - extent = "358 210"; + extent = "358 252"; minExtent = "8 8"; horizSizing = "right"; vertSizing = "bottom"; @@ -1252,6 +1254,66 @@ canSave = "1"; canSaveDynamicFields = "0"; }; + new GuiControl() { + position = "0 227"; + extent = "352 15"; + minExtent = "8 2"; + horizSizing = "width"; + vertSizing = "bottom"; + profile = "GuiDefaultProfile"; + visible = "1"; + active = "1"; + tooltipProfile = "GuiToolTipProfile"; + hovertime = "1000"; + isContainer = "1"; + internalName = "GammaSliderContainer"; + canSave = "1"; + canSaveDynamicFields = "0"; + + new GuiSliderCtrl() { + range = "0.5 1.5"; + ticks = "0"; + snap = "0"; + value = "1"; + position = "76 -1"; + extent = "268 15"; + minExtent = "8 2"; + horizSizing = "right"; + vertSizing = "bottom"; + profile = "GuiSliderProfile"; + visible = "1"; + active = "1"; + variable = "$pref::Video::Contrast"; + tooltipProfile = "GuiToolTipProfile"; + hovertime = "1000"; + isContainer = "0"; + canSave = "1"; + canSaveDynamicFields = "0"; + }; + new GuiTextCtrl() { + text = "Contrast:"; + maxLength = "255"; + margin = "0 0 0 0"; + padding = "0 0 0 0"; + anchorTop = "1"; + anchorBottom = "0"; + anchorLeft = "1"; + anchorRight = "0"; + position = "18 -4"; + extent = "105 18"; + minExtent = "8 2"; + horizSizing = "right"; + vertSizing = "bottom"; + profile = "GuiTextProfile"; + visible = "1"; + active = "1"; + tooltipProfile = "GuiToolTipProfile"; + hovertime = "1000"; + isContainer = "0"; + canSave = "1"; + canSaveDynamicFields = "0"; + }; + }; new GuiControl() { position = "0 190"; extent = "352 15"; @@ -1269,10 +1331,10 @@ canSaveDynamicFields = "0"; new GuiSliderCtrl() { - range = "0.001 2.2"; + range = "2.0 2.5"; ticks = "0"; snap = "0"; - value = "1"; + value = "2.2"; position = "76 -1"; extent = "268 15"; minExtent = "8 2"; @@ -1312,6 +1374,66 @@ canSaveDynamicFields = "0"; }; }; + new GuiControl() { + position = "0 208"; + extent = "352 15"; + minExtent = "8 2"; + horizSizing = "width"; + vertSizing = "bottom"; + profile = "GuiDefaultProfile"; + visible = "1"; + active = "1"; + tooltipProfile = "GuiToolTipProfile"; + hovertime = "1000"; + isContainer = "1"; + internalName = "GammaSliderContainer"; + canSave = "1"; + canSaveDynamicFields = "0"; + + new GuiSliderCtrl() { + range = "-0.5 0.5"; + ticks = "0"; + snap = "0"; + value = "0"; + position = "76 -1"; + extent = "268 15"; + minExtent = "8 2"; + horizSizing = "right"; + vertSizing = "bottom"; + profile = "GuiSliderProfile"; + visible = "1"; + active = "1"; + variable = "$pref::Video::Brightness"; + tooltipProfile = "GuiToolTipProfile"; + hovertime = "1000"; + isContainer = "0"; + canSave = "1"; + canSaveDynamicFields = "0"; + }; + new GuiTextCtrl() { + text = "Brightness:"; + maxLength = "255"; + margin = "0 0 0 0"; + padding = "0 0 0 0"; + anchorTop = "1"; + anchorBottom = "0"; + anchorLeft = "1"; + anchorRight = "0"; + position = "6 -3"; + extent = "105 18"; + minExtent = "8 2"; + horizSizing = "right"; + vertSizing = "bottom"; + profile = "GuiTextProfile"; + visible = "1"; + active = "1"; + tooltipProfile = "GuiToolTipProfile"; + hovertime = "1000"; + isContainer = "0"; + canSave = "1"; + canSaveDynamicFields = "0"; + }; + }; }; new GuiControl() { position = "9 55"; @@ -1396,7 +1518,7 @@ groupNum = "-1"; buttonType = "PushButton"; useMouseEvents = "0"; - position = "241 271"; + position = "239 319"; extent = "60 23"; minExtent = "8 8"; horizSizing = "right"; diff --git a/Templates/Full/game/core/scripts/client/defaults.cs b/Templates/Full/game/core/scripts/client/defaults.cs index 0142a9410..d1805d337 100644 --- a/Templates/Full/game/core/scripts/client/defaults.cs +++ b/Templates/Full/game/core/scripts/client/defaults.cs @@ -73,7 +73,9 @@ $pref::Video::disableCubemapping = false; /// $pref::Video::disableParallaxMapping = false; -$pref::Video::Gamma = 1.0; +$pref::Video::Gamma = 2.2; +$pref::Video::Contrast = 1.0; +$pref::Video::Brightness = 0; // Console-friendly defaults if($platform $= "xenon") diff --git a/Templates/Full/game/core/scripts/client/postFx/GammaPostFX.cs b/Templates/Full/game/core/scripts/client/postFx/GammaPostFX.cs index 383a0c8cd..b88f31305 100644 --- a/Templates/Full/game/core/scripts/client/postFx/GammaPostFX.cs +++ b/Templates/Full/game/core/scripts/client/postFx/GammaPostFX.cs @@ -44,7 +44,7 @@ singleton GFXStateBlockData( GammaStateBlock : PFX_DefaultStateBlock ) singleton PostEffect( GammaPostFX ) { isEnabled = true; - allowReflectPass = false; + allowReflectPass = true; renderTime = "PFXBeforeBin"; renderBin = "EditorBin"; @@ -65,6 +65,8 @@ function GammaPostFX::preProcess( %this ) function GammaPostFX::setShaderConsts( %this ) { - %clampedGamma = mClamp( $pref::Video::Gamma, 0.001, 2.2); + %clampedGamma = mClamp( $pref::Video::Gamma, 2.0, 2.5); %this.setShaderConst( "$OneOverGamma", 1 / %clampedGamma ); + %this.setShaderConst( "$Brightness", $pref::Video::Brightness ); + %this.setShaderConst( "$Contrast", $pref::Video::Contrast ); } \ No newline at end of file diff --git a/Templates/Full/game/core/scripts/client/postFx/hdr.cs b/Templates/Full/game/core/scripts/client/postFx/hdr.cs index a5c450799..136a5ca95 100644 --- a/Templates/Full/game/core/scripts/client/postFx/hdr.cs +++ b/Templates/Full/game/core/scripts/client/postFx/hdr.cs @@ -253,8 +253,10 @@ function HDRPostFX::setShaderConsts( %this ) %combinePass.setShaderConst( "$g_fEnableBlueShift", $HDRPostFX::enableBlueShift ); %combinePass.setShaderConst( "$g_fBlueShiftColor", $HDRPostFX::blueShiftColor ); - %clampedGamma = mClamp( $pref::Video::Gamma, 0.001, 2.2); + %clampedGamma = mClamp( $pref::Video::Gamma, 2.0, 2.5); %combinePass.setShaderConst( "$g_fOneOverGamma", 1 / %clampedGamma ); + %combinePass.setShaderConst( "$Brightness", $pref::Video::Brightness ); + %combinePass.setShaderConst( "$Contrast", $pref::Video::Contrast ); %whiteCutoff = ( $HDRPostFX::whiteCutoff * $HDRPostFX::whiteCutoff ) * ( $HDRPostFX::whiteCutoff * $HDRPostFX::whiteCutoff ); @@ -329,7 +331,7 @@ function HDRPostFX::onDisabled( %this ) singleton PostEffect( HDRPostFX ) { isEnabled = false; - allowReflectPass = false; + allowReflectPass = true; // Resolve the HDR before we render any editor stuff // and before we resolve the scene to the backbuffer. @@ -355,6 +357,7 @@ singleton PostEffect( HDRPostFX ) new PostEffect() { + allowReflectPass = true; shader = HDR_DownScale4x4Shader; stateBlock = HDR_DownSampleStateBlock; texture[0] = "$inTex"; @@ -365,6 +368,7 @@ singleton PostEffect( HDRPostFX ) new PostEffect() { + allowReflectPass = true; internalName = "bloomH"; shader = HDR_BloomGaussBlurHShader; @@ -376,6 +380,7 @@ singleton PostEffect( HDRPostFX ) new PostEffect() { + allowReflectPass = true; internalName = "bloomV"; shader = HDR_BloomGaussBlurVShader; @@ -390,6 +395,7 @@ singleton PostEffect( HDRPostFX ) // Now calculate the adapted luminance. new PostEffect() { + allowReflectPass = true; internalName = "adaptLum"; shader = HDR_SampleLumShader; @@ -401,6 +407,7 @@ singleton PostEffect( HDRPostFX ) new PostEffect() { + allowReflectPass = true; shader = HDR_DownSampleLumShader; stateBlock = HDR_DownSampleStateBlock; texture[0] = "$inTex"; @@ -411,6 +418,7 @@ singleton PostEffect( HDRPostFX ) new PostEffect() { + allowReflectPass = true; shader = HDR_DownSampleLumShader; stateBlock = HDR_DownSampleStateBlock; texture[0] = "$inTex"; @@ -421,6 +429,7 @@ singleton PostEffect( HDRPostFX ) new PostEffect() { + allowReflectPass = true; shader = HDR_DownSampleLumShader; stateBlock = HDR_DownSampleStateBlock; texture[0] = "$inTex"; @@ -434,6 +443,7 @@ singleton PostEffect( HDRPostFX ) // one... PostEffect takes care to manage that. new PostEffect() { + allowReflectPass = true; internalName = "finalLum"; shader = HDR_CalcAdaptedLumShader; stateBlock = HDR_DownSampleStateBlock; @@ -450,6 +460,7 @@ singleton PostEffect( HDRPostFX ) // version of the scene. new PostEffect() { + allowReflectPass = true; internalName = "combinePass"; shader = HDR_CombineShader; diff --git a/Templates/Full/game/core/scripts/client/renderManager.cs b/Templates/Full/game/core/scripts/client/renderManager.cs index dcd1628fe..5559fd70a 100644 --- a/Templates/Full/game/core/scripts/client/renderManager.cs +++ b/Templates/Full/game/core/scripts/client/renderManager.cs @@ -33,7 +33,7 @@ function initRenderManager() { enabled = "false"; - format = "GFXFormatR8G8B8A8"; + format = "GFXFormatR16G16B16A16F"; depthFormat = "GFXFormatD24S8"; aaLevel = 0; // -1 = match backbuffer @@ -49,20 +49,21 @@ function initRenderManager() // We really need to fix the sky to render after all the // meshes... but that causes issues in reflections. - DiffuseRenderPassManager.addManager( new RenderObjectMgr() { bintype = "Sky"; renderOrder = 0.1; processAddOrder = 0.1; } ); + DiffuseRenderPassManager.addManager( new RenderObjectMgr(SkyBin) { bintype = "Sky"; renderOrder = 0.1; processAddOrder = 0.1; } ); //DiffuseRenderPassManager.addManager( new RenderVistaMgr() { bintype = "Vista"; renderOrder = 0.15; processAddOrder = 0.15; } ); - DiffuseRenderPassManager.addManager( new RenderObjectMgr() { bintype = "Begin"; renderOrder = 0.2; processAddOrder = 0.2; } ); + DiffuseRenderPassManager.addManager( new RenderObjectMgr(BeginBin) { bintype = "Begin"; renderOrder = 0.2; processAddOrder = 0.2; } ); // Normal mesh rendering. - DiffuseRenderPassManager.addManager( new RenderTerrainMgr() { renderOrder = 0.4; processAddOrder = 0.4; } ); - DiffuseRenderPassManager.addManager( new RenderMeshMgr() { bintype = "Mesh"; renderOrder = 0.5; processAddOrder = 0.5; } ); - DiffuseRenderPassManager.addManager( new RenderImposterMgr() { renderOrder = 0.56; processAddOrder = 0.56; } ); - DiffuseRenderPassManager.addManager( new RenderObjectMgr() { bintype = "Object"; renderOrder = 0.6; processAddOrder = 0.6; } ); - - DiffuseRenderPassManager.addManager( new RenderObjectMgr() { bintype = "Shadow"; renderOrder = 0.7; processAddOrder = 0.7; } ); - DiffuseRenderPassManager.addManager( new RenderMeshMgr() { bintype = "Decal"; renderOrder = 0.8; processAddOrder = 0.8; } ); - DiffuseRenderPassManager.addManager( new RenderOcclusionMgr() { bintype = "Occluder"; renderOrder = 0.9; processAddOrder = 0.9; } ); + DiffuseRenderPassManager.addManager( new RenderTerrainMgr(TerrainBin) { renderOrder = 0.4; processAddOrder = 0.4; } ); + DiffuseRenderPassManager.addManager( new RenderMeshMgr(MeshBin) { bintype = "Mesh"; renderOrder = 0.5; processAddOrder = 0.5; } ); + DiffuseRenderPassManager.addManager( new RenderImposterMgr(ImposterBin) { renderOrder = 0.56; processAddOrder = 0.56; } ); + DiffuseRenderPassManager.addManager( new RenderObjectMgr(ObjectBin) { bintype = "Object"; renderOrder = 0.6; processAddOrder = 0.6; } ); + + DiffuseRenderPassManager.addManager( new RenderObjectMgr(ShadowBin) { bintype = "Shadow"; renderOrder = 0.7; processAddOrder = 0.7; } ); + DiffuseRenderPassManager.addManager( new RenderMeshMgr(DecalRoadBin) { bintype = "DecalRoad"; renderOrder = 0.8; processAddOrder = 0.8; } ); + DiffuseRenderPassManager.addManager( new RenderMeshMgr(DecalBin) { bintype = "Decal"; renderOrder = 0.81; processAddOrder = 0.81; } ); + DiffuseRenderPassManager.addManager( new RenderOcclusionMgr(OccluderBin){ bintype = "Occluder"; renderOrder = 0.9; processAddOrder = 0.9; } ); // We now render translucent objects that should handle // their own fogging and lighting. @@ -70,10 +71,10 @@ function initRenderManager() // Note that the fog effect is triggered before this bin. DiffuseRenderPassManager.addManager( new RenderObjectMgr(ObjTranslucentBin) { bintype = "ObjectTranslucent"; renderOrder = 1.0; processAddOrder = 1.0; } ); - DiffuseRenderPassManager.addManager( new RenderObjectMgr() { bintype = "Water"; renderOrder = 1.2; processAddOrder = 1.2; } ); - DiffuseRenderPassManager.addManager( new RenderObjectMgr() { bintype = "Foliage"; renderOrder = 1.3; processAddOrder = 1.3; } ); - DiffuseRenderPassManager.addManager( new RenderParticleMgr() { renderOrder = 1.35; processAddOrder = 1.35; } ); - DiffuseRenderPassManager.addManager( new RenderTranslucentMgr() { renderOrder = 1.4; processAddOrder = 1.4; } ); + DiffuseRenderPassManager.addManager( new RenderObjectMgr(WaterBin) { bintype = "Water"; renderOrder = 1.2; processAddOrder = 1.2; } ); + DiffuseRenderPassManager.addManager( new RenderObjectMgr(FoliageBin) { bintype = "Foliage"; renderOrder = 1.3; processAddOrder = 1.3; } ); + DiffuseRenderPassManager.addManager( new RenderParticleMgr(ParticleBin) { renderOrder = 1.35; processAddOrder = 1.35; } ); + DiffuseRenderPassManager.addManager( new RenderTranslucentMgr(TranslucentBin){ renderOrder = 1.4; processAddOrder = 1.4; } ); // Note that the GlowPostFx is triggered after this bin. DiffuseRenderPassManager.addManager( new RenderGlowMgr(GlowBin) { renderOrder = 1.5; processAddOrder = 1.5; } ); @@ -83,7 +84,7 @@ function initRenderManager() DiffuseRenderPassManager.addManager( new RenderObjectMgr(EditorBin) { bintype = "Editor"; renderOrder = 1.6; processAddOrder = 1.6; } ); // Resolve format change token last. - DiffuseRenderPassManager.addManager( new RenderPassStateBin() { renderOrder = 1.7; stateToken = AL_FormatToken; } ); + DiffuseRenderPassManager.addManager( new RenderPassStateBin(FinalBin) { renderOrder = 1.7; stateToken = AL_FormatToken; } ); } /// This post effect is used to copy data from the non-MSAA back-buffer to the diff --git a/Templates/Full/game/shaders/common/gl/torque.glsl b/Templates/Full/game/shaders/common/gl/torque.glsl index 9032a57f7..65580cb7b 100644 --- a/Templates/Full/game/shaders/common/gl/torque.glsl +++ b/Templates/Full/game/shaders/common/gl/torque.glsl @@ -284,4 +284,37 @@ void fizzle(vec2 vpos, float visibility) /// @note This macro will only work in the void main() method of a pixel shader. #define assert(condition, color) { if(!any(condition)) { OUT_col = color; return; } } +// Deferred Shading: Material Info Flag Check +bool getFlag(float flags, int num) +{ + float process = round(flags * 255); + float squareNum = pow(2, num); + return (mod(process, pow(2, squareNum)) >= squareNum); +} + +// #define TORQUE_STOCK_GAMMA +#ifdef TORQUE_STOCK_GAMMA +// Sample in linear space. Decodes gamma. +vec4 toLinear(vec4 tex) +{ + return tex; +} +// Encodes gamma. +vec4 toGamma(vec4 tex) +{ + return tex; +} +#else +// Sample in linear space. Decodes gamma. +vec4 toLinear(vec4 tex) +{ + return vec4(pow(abs(tex.rgb), vec3(2.2)), tex.a); +} +// Encodes gamma. +vec4 toGamma(vec4 tex) +{ + return vec4(pow(abs(tex.rgb), vec3(1.0/2.2)), tex.a); +} +#endif // + #endif // _TORQUE_GLSL_ diff --git a/Templates/Full/game/shaders/common/postFx/gammaP.hlsl b/Templates/Full/game/shaders/common/postFx/gammaP.hlsl index dedfe8eb5..20196548b 100644 --- a/Templates/Full/game/shaders/common/postFx/gammaP.hlsl +++ b/Templates/Full/game/shaders/common/postFx/gammaP.hlsl @@ -28,7 +28,8 @@ uniform sampler2D backBuffer : register(S0); uniform sampler1D colorCorrectionTex : register( s1 ); uniform float OneOverGamma; - +uniform float Brightness; +uniform float Contrast; float4 main( PFXVertToPix IN ) : COLOR0 { @@ -42,5 +43,11 @@ float4 main( PFXVertToPix IN ) : COLOR0 // Apply gamma correction color.rgb = pow( abs(color.rgb), OneOverGamma ); + // Apply contrast + color.rgb = ((color.rgb - 0.5f) * Contrast) + 0.5f; + + // Apply brightness + color.rgb += Brightness; + return color; } \ No newline at end of file diff --git a/Templates/Full/game/shaders/common/postFx/gl/gammaP.glsl b/Templates/Full/game/shaders/common/postFx/gl/gammaP.glsl index 414a277d3..1bf5d1b8f 100644 --- a/Templates/Full/game/shaders/common/postFx/gl/gammaP.glsl +++ b/Templates/Full/game/shaders/common/postFx/gl/gammaP.glsl @@ -28,6 +28,8 @@ uniform sampler2D backBuffer; uniform sampler1D colorCorrectionTex; uniform float OneOverGamma; +uniform float Brightness; +uniform float Contrast; in vec2 uv0; @@ -45,5 +47,11 @@ void main() // Apply gamma correction color.rgb = pow( abs(color.rgb), vec3(OneOverGamma) ); + // Apply contrast + color.rgb = ((color.rgb - 0.5f) * Contrast) + 0.5f; + + // Apply brightness + color.rgb += Brightness; + OUT_col = color; } \ No newline at end of file diff --git a/Templates/Full/game/shaders/common/postFx/hdr/finalPassCombineP.hlsl b/Templates/Full/game/shaders/common/postFx/hdr/finalPassCombineP.hlsl index 7ac71bebd..9541f39cb 100644 --- a/Templates/Full/game/shaders/common/postFx/hdr/finalPassCombineP.hlsl +++ b/Templates/Full/game/shaders/common/postFx/hdr/finalPassCombineP.hlsl @@ -41,6 +41,8 @@ uniform float3 g_fBlueShiftColor; uniform float g_fBloomScale; uniform float g_fOneOverGamma; +uniform float Brightness; +uniform float Contrast; float4 main( PFXVertToPix IN ) : COLOR0 @@ -90,6 +92,12 @@ float4 main( PFXVertToPix IN ) : COLOR0 // Apply gamma correction sample.rgb = pow( abs(sample.rgb), g_fOneOverGamma ); + + // Apply contrast + sample.rgb = ((sample.rgb - 0.5f) * Contrast) + 0.5f; + + // Apply brightness + sample.rgb += Brightness; return sample; } diff --git a/Templates/Full/game/shaders/common/postFx/hdr/gl/finalPassCombineP.glsl b/Templates/Full/game/shaders/common/postFx/hdr/gl/finalPassCombineP.glsl index 38762baa5..123c831f3 100644 --- a/Templates/Full/game/shaders/common/postFx/hdr/gl/finalPassCombineP.glsl +++ b/Templates/Full/game/shaders/common/postFx/hdr/gl/finalPassCombineP.glsl @@ -42,6 +42,8 @@ uniform vec3 g_fBlueShiftColor; uniform float g_fBloomScale; uniform float g_fOneOverGamma; +uniform float Brightness; +uniform float Contrast; out vec4 OUT_col; @@ -93,6 +95,12 @@ void main() // Apply gamma correction _sample.rgb = pow( abs(_sample.rgb), vec3(g_fOneOverGamma) ); + + // Apply contrast + _sample.rgb = ((_sample.rgb - 0.5f) * Contrast) + 0.5f; + + // Apply brightness + _sample.rgb += Brightness; OUT_col = _sample; } diff --git a/Templates/Full/game/shaders/common/torque.hlsl b/Templates/Full/game/shaders/common/torque.hlsl index 1d253936b..f50832cb8 100644 --- a/Templates/Full/game/shaders/common/torque.hlsl +++ b/Templates/Full/game/shaders/common/torque.hlsl @@ -277,5 +277,37 @@ void fizzle(float2 vpos, float visibility) clip( visibility - frac( determinant( m ) ) ); } +// Deferred Shading: Material Info Flag Check +bool getFlag(float flags, int num) +{ + int process = round(flags * 255); + int squareNum = pow(2, num); + return (fmod(process, pow(2, squareNum)) >= squareNum); +} + +// #define TORQUE_STOCK_GAMMA +#ifdef TORQUE_STOCK_GAMMA +// Sample in linear space. Decodes gamma. +float4 toLinear(float4 tex) +{ + return tex; +} +// Encodes gamma. +float4 toLinear(float4 tex) +{ + return tex; +} +#else +// Sample in linear space. Decodes gamma. +float4 toLinear(float4 tex) +{ + return float4(pow(abs(tex.rgb), 2.2), tex.a); +} +// Encodes gamma. +float4 toGamma(float4 tex) +{ + return float4(pow(abs(tex.rgb), 1.0/2.2), tex.a); +} +#endif // #endif // _TORQUE_HLSL_ diff --git a/Templates/Full/game/shaders/common/water/gl/waterBasicP.glsl b/Templates/Full/game/shaders/common/water/gl/waterBasicP.glsl index 82c421031..1d5a07c3f 100644 --- a/Templates/Full/game/shaders/common/water/gl/waterBasicP.glsl +++ b/Templates/Full/game/shaders/common/water/gl/waterBasicP.glsl @@ -120,6 +120,7 @@ void main() { // Modulate baseColor by the ambientColor. vec4 waterBaseColor = baseColor * vec4( ambientColor.rgb, 1 ); + waterBaseColor = toLinear(waterBaseColor); // Get the bumpNorm... vec3 bumpNorm = ( texture( bumpMap, IN_rippleTexCoord01.xy ).rgb * 2.0 - 1.0 ) * rippleMagnitude.x; diff --git a/Templates/Full/game/shaders/common/water/gl/waterP.glsl b/Templates/Full/game/shaders/common/water/gl/waterP.glsl index af151020a..15002849f 100644 --- a/Templates/Full/game/shaders/common/water/gl/waterP.glsl +++ b/Templates/Full/game/shaders/common/water/gl/waterP.glsl @@ -324,6 +324,7 @@ void main() // Calculate the water "base" color based on depth. vec4 waterBaseColor = baseColor * texture( depthGradMap, saturate( delta / depthGradMax ) ); + waterBaseColor = toLinear(waterBaseColor); // Modulate baseColor by the ambientColor. waterBaseColor *= vec4( ambientColor.rgb, 1 ); diff --git a/Templates/Full/game/shaders/common/water/waterBasicP.hlsl b/Templates/Full/game/shaders/common/water/waterBasicP.hlsl index d27b28c67..7ae933f6f 100644 --- a/Templates/Full/game/shaders/common/water/waterBasicP.hlsl +++ b/Templates/Full/game/shaders/common/water/waterBasicP.hlsl @@ -117,6 +117,7 @@ float4 main( ConnectData IN ) : COLOR { // Modulate baseColor by the ambientColor. float4 waterBaseColor = baseColor * float4( ambientColor.rgb, 1 ); + waterBaseColor = toLinear(waterBaseColor); // Get the bumpNorm... float3 bumpNorm = ( tex2D( bumpMap, IN.rippleTexCoord01.xy ).rgb * 2.0 - 1.0 ) * rippleMagnitude.x; diff --git a/Templates/Full/game/shaders/common/water/waterP.hlsl b/Templates/Full/game/shaders/common/water/waterP.hlsl index 851fb471f..2b4035755 100644 --- a/Templates/Full/game/shaders/common/water/waterP.hlsl +++ b/Templates/Full/game/shaders/common/water/waterP.hlsl @@ -311,6 +311,7 @@ float4 main( ConnectData IN ) : COLOR // Calculate the water "base" color based on depth. float4 waterBaseColor = baseColor * tex1D( depthGradMap, saturate( delta / depthGradMax ) ); + waterBaseColor = toLinear(waterBaseColor); // Modulate baseColor by the ambientColor. waterBaseColor *= float4( ambientColor.rgb, 1 ); From d3c7edfe4290a6b87182dc9a79f410b684b67093 Mon Sep 17 00:00:00 2001 From: Robert MacGregor Date: Sun, 22 Nov 2015 02:45:25 -0500 Subject: [PATCH 046/109] Fix TinyXML Build errors --- Engine/source/console/consoleObject.h | 110 +++++++++++++------------- 1 file changed, 53 insertions(+), 57 deletions(-) diff --git a/Engine/source/console/consoleObject.h b/Engine/source/console/consoleObject.h index 5e585247e..5f04435aa 100644 --- a/Engine/source/console/consoleObject.h +++ b/Engine/source/console/consoleObject.h @@ -48,7 +48,7 @@ #include "console/simObjectRef.h" #endif #ifndef TINYXML_INCLUDED - #include "tinyXML/tinyxml.h" + #include "tinyxml.h" #endif /// @file @@ -212,7 +212,7 @@ public: /// /// @param conIdPtr Pointer to the static S32 console ID. /// @param conTypeName Console type name. - AbstractClassRep( S32* conIdPtr, const char* typeName ) + AbstractClassRep( S32* conIdPtr, const char* typeName ) : Parent( sizeof( void* ), conIdPtr, typeName ) { VECTOR_SET_ASSOCIATION( mFieldList ); @@ -323,13 +323,13 @@ public: /// Return the namespace that contains the methods of this class. Namespace* getNameSpace() const { return mNamespace; } - + /// Return the AbstractClassRep of the class that this class is derived from. AbstractClassRep* getParentClass() const { return parentClass; } virtual AbstractClassRep* getContainerChildClass(const bool recurse) = 0; virtual WriteCustomTamlSchema getCustomTamlSchema(void) = 0; - + /// Return the size of instances of this class in bytes. S32 getSizeof() const { return mClassSizeof; } @@ -396,7 +396,7 @@ protected: Namespace * mNamespace; /// @} - + public: bool mIsRenderEnabled; @@ -404,23 +404,23 @@ public: bool isRenderEnabled() const { return mIsRenderEnabled; } bool isSelectionEnabled() const { return mIsSelectionEnabled; } - + /// @name Categories /// @{ - + protected: const char* mCategory; const char* mDescription; - + public: /// Return the space separated category path for the class. const char* getCategory() const { return mCategory; } - + /// Return a short description string suitable for displaying in tooltips. const char* getDescription() const { return mDescription; } - + /// @} /// @name Fields @@ -434,16 +434,12 @@ public: /// This is a function pointer typedef to support optional writing for fields. typedef bool(*WriteDataNotify)(void* obj, StringTableEntry pFieldName); - /// Allows the writing of a custom TAML schema. - typedef void(*WriteCustomTamlSchema)(const AbstractClassRep* pClassRep, TiXmlElement* pParentElement); - - /// These are special field type values used to mark /// groups and arrays in the field list. /// @see Field::type /// @see addArray, endArray - /// @see addGroup, endGroup - /// @see addGroup, endGroup + /// @see addGroup, endGroup + /// @see addGroup, endGroup /// @see addDeprecatedField enum ACRFieldTypes { @@ -451,35 +447,35 @@ public: /// types greater or equal to this one are not /// console data types. ARCFirstCustomField = 0xFFFFFFFB, - + /// Marks the start of a fixed size array of fields. /// @see addArray StartArrayFieldType = 0xFFFFFFFB, - + /// Marks the end of a fixed size array of fields. /// @see endArray EndArrayFieldType = 0xFFFFFFFC, - + /// Marks the beginning of a group of fields. /// @see addGroup StartGroupFieldType = 0xFFFFFFFD, - + /// Marks the beginning of a group of fields. /// @see endGroup EndGroupFieldType = 0xFFFFFFFE, - - /// Marks a field that is depreciated and no + + /// Marks a field that is depreciated and no /// longer stores a value. /// @see addDeprecatedField DeprecatedFieldType = 0xFFFFFFFF }; - + enum FieldFlags { FIELD_HideInInspectors = BIT( 0 ), ///< Do not show the field in inspectors. }; - struct Field + struct Field { Field() : pFieldname( NULL ), @@ -525,10 +521,10 @@ public: /// @name Console Type Interface /// @{ - + virtual void* getNativeVariable() { return new ( AbstractClassRep* ); } // Any pointer-sized allocation will do. virtual void deleteNativeVariable( void* var ) { delete reinterpret_cast< AbstractClassRep** >( var ); } - + /// @} /// @name Abstract Class Database @@ -574,10 +570,10 @@ template< class T > class ConcreteClassRep : public AbstractClassRep { public: - + static EnginePropertyTable _smPropertyTable; static EnginePropertyTable& smPropertyTable; - + ConcreteClassRep( const char* name, const char* conTypeName, S32* conTypeIdPtr, @@ -591,10 +587,10 @@ class ConcreteClassRep : public AbstractClassRep mClassName = StringTable->insert( name ); mCategory = T::__category(); mTypeInfo = _MAPTYPE< T >(); - + if( mTypeInfo ) const_cast< EngineTypeInfo* >( mTypeInfo )->mPropertyTable = &smPropertyTable; - + if( &T::__description != parentDesc ) mDescription = T::__description(); @@ -642,7 +638,7 @@ class ConcreteClassRep : public AbstractClassRep // Get handle to our parent class, if any, and ourselves (we are our parent's child). AbstractClassRep *parent = T::getParentStaticClassRep(); AbstractClassRep *child = T::getStaticClassRep(); - + // If we got reps, then link those namespaces! (To get proper inheritance.) if(parent && child) Con::classLinkNamespaces(parent->getNameSpace(), child->getNameSpace()); @@ -657,7 +653,7 @@ class ConcreteClassRep : public AbstractClassRep /// Wrap constructor. ConsoleObject* create() const { return new T; } - + /// @name Console Type Interface /// @{ @@ -671,16 +667,16 @@ class ConcreteClassRep : public AbstractClassRep else Con::errorf( "Cannot set multiple args to a single ConsoleObject*."); } - + virtual const char* getData( void* dptr, const EnumTable* tbl, BitSet32 flag ) { T** obj = ( T** ) dptr; return Con::getReturnBuffer( T::__getObjectId( *obj ) ); } - + virtual const char* getTypeClassName() { return mClassName; } virtual const bool isDatablock() { return T::__smIsDatablock; }; - + /// @} }; @@ -751,7 +747,7 @@ bool defaultProtectedWriteFn(void* obj, StringTableEntry pFieldName); class ConsoleObject : public EngineObject { DECLARE_ABSTRACT_CLASS( ConsoleObject, EngineObject ); - + protected: /// @deprecated This is disallowed. @@ -760,7 +756,7 @@ protected: public: ConsoleObject() {} - + /// Get a reference to a field by name. const AbstractClassRep::Field *findField(StringTableEntry fieldName) const; @@ -769,7 +765,7 @@ public: /// Set the value of a field. bool setField(const char *fieldName, const char *value); - + public: /// @name Object Creation @@ -799,11 +795,11 @@ public: static void endGroup(const char* in_pGroupname); /// Marks the start of a fixed size array of fields. - /// @see console_autodoc + /// @see console_autodoc static void addArray( const char *arrayName, S32 count ); /// Marks the end of an array of fields. - /// @see console_autodoc + /// @see console_autodoc static void endArray( const char *arrayName ); /// Register a complex field. @@ -928,16 +924,16 @@ public: static bool removeField(const char* in_pFieldname); /// @} - + /// @name Logging /// @{ - + /// Overload this in subclasses to change the message formatting. /// @param fmt A printf style format string. /// @param args A va_list containing the args passed ot a log function. /// @note It is suggested that you use String::VToString. virtual String _getLogMessage(const char* fmt, va_list args) const; - + /// @} public: @@ -946,16 +942,16 @@ public: /// These functions will try to print out a message along the lines /// of "ObjectClass - ObjectName(ObjectId) - formatted message" /// @{ - + /// Logs with Con::printf. void logMessage(const char* fmt, ...) const; - + /// Logs with Con::warnf. void logWarning(const char* fmt, ...) const; - + /// Logs with Con::errorf. void logError(const char* fmt, ...) const; - + /// @} /// Register dynamic fields in a subclass of ConsoleObject. @@ -1016,16 +1012,16 @@ public: static const char* __category() { return ""; } static const char* __description() { return ""; } - + /// Subclasses of ConsoleObjects that are datablocks should redefine this static member variable /// and set it to true. static const bool __smIsDatablock = false; - + /// @name Object IDs and lookup. /// For a subclass hierarchy based on ConsoleObject to become functional for use as a console object type, /// the hierarchy must implement a naming scheme and indexing function for looking up objects by name. /// @{ - + static ConsoleObject* __findObject( const char* ) { return NULL; } static const char* __getObjectId( ConsoleObject* ) { return ""; } }; @@ -1120,11 +1116,11 @@ inline bool& ConsoleObject::getDynamicGroupExpand() static SimObjectRefConsoleBaseType< className > ptrRefType; \ static AbstractClassRep::WriteCustomTamlSchema getStaticWriteCustomTamlSchema(); \ static AbstractClassRep* getContainerChildStaticClassRep(); \ - virtual AbstractClassRep* getClassRep() const - + virtual AbstractClassRep* getClassRep() const + #define DECLARE_CATEGORY( string ) \ static const char* __category() { return string; } - + #define DECLARE_DESCRIPTION( string ) \ static const char* __description() { return string; } @@ -1199,7 +1195,7 @@ inline bool& ConsoleObject::getDynamicGroupExpand() AbstractClassRep* className::getContainerChildStaticClassRep() { return NULL; } \ AbstractClassRep::WriteCustomTamlSchema className::getStaticWriteCustomTamlSchema() { return NULL; } \ ConcreteClassRep className::dynClassRep(#className, "Type" #className, &_smTypeId, NetClassGroupGameMask, NetClassTypeDataBlock, 0, className::getParentStaticClassRep(), &Parent::__description ) - + // Support for adding properties to classes CONOBJECT style. #define PROPERTY_TABLE( className ) \ namespace { namespace _ ## className { \ @@ -1209,13 +1205,13 @@ inline bool& ConsoleObject::getDynamicGroupExpand() ConcreteClassRep< className >::smPropertyTable = _ ## className::_propTable; \ namespace { namespace _ ## className { \ EnginePropertyTable::Property _props[] = { - + #define END_PROPERTY_TABLE \ { NULL } \ }; \ EnginePropertyTable _propTable( sizeof( _props ) / sizeof( _props[ 0 ] ) - 1, _props ); \ } } - + /// Add an auto-doc for a class. #define ConsoleDocClass( className, docString ) \ CLASSDOC( className, docString ) @@ -1225,7 +1221,7 @@ inline bool& ConsoleObject::getDynamicGroupExpand() //------------------------------------------------------------------------------ // Protected field default get/set functions // -// The reason for these functions is that it will save one branch per console +// The reason for these functions is that it will save one branch per console // data request and script functions will still execute at the same speed as // before the modifications to allow protected static fields. These will just // inline and the code should be roughly the same size, and just as fast as From b0b39b5f83cb55f79da537bcd854f4a44da64631 Mon Sep 17 00:00:00 2001 From: Azaezel Date: Fri, 27 Nov 2015 16:17:30 -0600 Subject: [PATCH 047/109] mDirtyTiles changed from std::queue to a vector allows us to leverage .push_back_unique(&foo); and .clear(); --- Engine/source/navigation/navMesh.cpp | 16 ++++++++-------- Engine/source/navigation/navMesh.h | 2 +- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/Engine/source/navigation/navMesh.cpp b/Engine/source/navigation/navMesh.cpp index 7df05ee2e..02de1b071 100644 --- a/Engine/source/navigation/navMesh.cpp +++ b/Engine/source/navigation/navMesh.cpp @@ -637,7 +637,7 @@ DefineEngineMethod(NavMesh, build, bool, (bool background, bool save), (true, fa void NavMesh::cancelBuild() { - while(!mDirtyTiles.empty()) mDirtyTiles.pop(); + mDirtyTiles.clear(); ctx->stopTimer(RC_TIMER_TOTAL); mBuilding = false; } @@ -707,7 +707,7 @@ void NavMesh::updateTiles(bool dirty) mTiles.clear(); mTileData.clear(); - while(!mDirtyTiles.empty()) mDirtyTiles.pop(); + mDirtyTiles.clear(); const Box3F &box = DTStoRC(getWorldBox()); if(box.isEmpty()) @@ -741,7 +741,7 @@ void NavMesh::updateTiles(bool dirty) tileBmin, tileBmax)); if(dirty) - mDirtyTiles.push(mTiles.size() - 1); + mDirtyTiles.push_back_unique(mTiles.size() - 1); if(mSaveIntermediates) mTileData.increment(); @@ -760,7 +760,7 @@ void NavMesh::buildNextTile() { // Pop a single dirty tile and process it. U32 i = mDirtyTiles.front(); - mDirtyTiles.pop(); + mDirtyTiles.pop_front(); const Tile &tile = mTiles[i]; // Intermediate data for tile build. TileData tempdata; @@ -844,7 +844,7 @@ unsigned char *NavMesh::buildTileData(const Tile &tile, TileData &data, U32 &dat // Check for no geometry. if(!data.geom.getVertCount()) - return false; + return NULL; // Figure out voxel dimensions of this tile. U32 width = 0, height = 0; @@ -1066,7 +1066,7 @@ void NavMesh::buildTiles(const Box3F &box) if(!tile.box.isOverlapped(box)) continue; // Mark as dirty. - mDirtyTiles.push(i); + mDirtyTiles.push_back_unique(i); } if(mDirtyTiles.size()) ctx->startTimer(RC_TIMER_TOTAL); @@ -1082,7 +1082,7 @@ void NavMesh::buildTile(const U32 &tile) { if(tile < mTiles.size()) { - mDirtyTiles.push(tile); + mDirtyTiles.push_back_unique(tile); ctx->startTimer(RC_TIMER_TOTAL); } } @@ -1104,7 +1104,7 @@ void NavMesh::buildLinks() mLinksUnsynced[j]) { // Mark tile for build. - mDirtyTiles.push(i); + mDirtyTiles.push_back_unique(i); // Delete link if necessary if(mDeleteLinks[j]) { diff --git a/Engine/source/navigation/navMesh.h b/Engine/source/navigation/navMesh.h index 1d9eba1b1..ff279c3be 100644 --- a/Engine/source/navigation/navMesh.h +++ b/Engine/source/navigation/navMesh.h @@ -325,7 +325,7 @@ private: Vector mTileData; /// List of indices to the tile array which are dirty. - std::queue mDirtyTiles; + Vector mDirtyTiles; /// Update tile dimensions. void updateTiles(bool dirty = false); From e63c0a78a3b924b2de3baae41d54d25abb14a928 Mon Sep 17 00:00:00 2001 From: Azaezel Date: Fri, 27 Nov 2015 17:43:08 -0600 Subject: [PATCH 048/109] NavMeshUpdateAll leak suppression (not 100% preventative) 1) Reset the build when adding potential dirties to the list to ensure it isn't trying to kill off a dirty entry that it's passed by. 2) mSaveIntermediates = true; causes leakage even with all this. still tracking that end. 3) Need to remove a dead tile whether there's new data to replace it with or not. Empty tiles are an entirely possible case. Even a probable one if you have high verticality in a level. 4) Likewise you don't want to re-feed the same geometry list for a given tile in case the conditions have changed. 5) If no vertcount then clear all geometry entries. (that one might be paranoia) --- Engine/source/navigation/navMesh.cpp | 22 ++++++++++++++++------ 1 file changed, 16 insertions(+), 6 deletions(-) diff --git a/Engine/source/navigation/navMesh.cpp b/Engine/source/navigation/navMesh.cpp index 7df05ee2e..5575327e8 100644 --- a/Engine/source/navigation/navMesh.cpp +++ b/Engine/source/navigation/navMesh.cpp @@ -113,7 +113,11 @@ DefineConsoleFunction(NavMeshUpdateAll, void, (S32 objid, bool remove), (0, fals for(U32 i = 0; i < set->size(); i++) { NavMesh *m = static_cast(set->at(i)); - m->buildTiles(obj->getWorldBox()); + if (m) + { + m->cancelBuild(); + m->buildTiles(obj->getWorldBox()); + } } if(remove) obj->enableCollision(); @@ -147,7 +151,7 @@ NavMesh::NavMesh() mFileName = StringTable->insert(""); mNetFlags.clear(Ghostable); - mSaveIntermediates = true; + mSaveIntermediates = false; nm = NULL; ctx = NULL; @@ -765,13 +769,15 @@ void NavMesh::buildNextTile() // Intermediate data for tile build. TileData tempdata; TileData &tdata = mSaveIntermediates ? mTileData[i] : tempdata; + + // Remove any previous data. + nm->removeTile(nm->getTileRefAt(tile.x, tile.y, 0), 0, 0); + // Generate navmesh for this tile. U32 dataSize = 0; unsigned char* data = buildTileData(tile, tdata, dataSize); if(data) { - // Remove any previous data. - nm->removeTile(nm->getTileRefAt(tile.x, tile.y, 0), 0, 0); // Add new data (navmesh owns and deletes the data). dtStatus status = nm->addTile(data, dataSize, DT_TILE_FREE_DATA, 0, 0); int success = 1; @@ -830,6 +836,7 @@ unsigned char *NavMesh::buildTileData(const Tile &tile, TileData &data, U32 &dat SceneContainer::CallbackInfo info; info.context = PLC_Navigation; info.boundingBox = box; + data.geom.clear(); info.polyList = &data.geom; info.key = this; getContainer()->findObjects(box, StaticShapeObjectType | TerrainObjectType, buildCallback, &info); @@ -843,8 +850,11 @@ unsigned char *NavMesh::buildTileData(const Tile &tile, TileData &data, U32 &dat } // Check for no geometry. - if(!data.geom.getVertCount()) - return false; + if (!data.geom.getVertCount()) + { + data.geom.clear(); + return NULL; + } // Figure out voxel dimensions of this tile. U32 width = 0, height = 0; From 45055f8f3e638b2fed977f54157f8798eea28e91 Mon Sep 17 00:00:00 2001 From: Azaezel Date: Fri, 27 Nov 2015 18:18:21 -0600 Subject: [PATCH 049/109] formatting --- Engine/source/navigation/navMesh.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Engine/source/navigation/navMesh.cpp b/Engine/source/navigation/navMesh.cpp index 5575327e8..738348d2f 100644 --- a/Engine/source/navigation/navMesh.cpp +++ b/Engine/source/navigation/navMesh.cpp @@ -113,8 +113,8 @@ DefineConsoleFunction(NavMeshUpdateAll, void, (S32 objid, bool remove), (0, fals for(U32 i = 0; i < set->size(); i++) { NavMesh *m = static_cast(set->at(i)); - if (m) - { + if (m) + { m->cancelBuild(); m->buildTiles(obj->getWorldBox()); } From a90eb9762b4bff4e3fb90576d1b653ce78d0e0b4 Mon Sep 17 00:00:00 2001 From: Areloch Date: Tue, 1 Dec 2015 00:10:13 -0600 Subject: [PATCH 050/109] Re-submission of the Volumetric Fog PR, with cleanup. --- Engine/source/gui/core/guiCanvas.cpp | 3 + Engine/source/gui/core/guiCanvas.h | 10 + .../renderInstance/renderPassManager.cpp | 1 + .../source/renderInstance/renderPassManager.h | 1 + .../renderInstance/renderTranslucentMgr.cpp | 10 + .../source/windowManager/platformWindow.cpp | 7 +- Engine/source/windowManager/platformWindow.h | 11 +- .../windowManager/win32/win32Window.cpp | 2 + .../game/art/environment/FogMod_heavy.dds | Bin 0 -> 131200 bytes .../game/art/environment/FogMod_light.dds | Bin 0 -> 131200 bytes .../Empty/game/art/environment/FogMod_med.dds | Bin 0 -> 131200 bytes .../Empty/game/art/environment/Fog_Cube.DAE | 177 ++++++++ .../Empty/game/art/environment/Fog_Cube.cs | 8 + .../game/core/scripts/client/postFx/glow.cs | 76 ++++ .../postFx/postFxManager.gui.settings.cs | 1 + .../game/core/scripts/client/renderManager.cs | 2 + .../game/scripts/server/VolumetricFog.cs | 106 +++++ .../Empty/game/scripts/server/scriptExec.cs | 1 + .../shaders/common/VolumetricFog/VFogP.hlsl | 87 ++++ .../common/VolumetricFog/VFogPreP.hlsl | 39 ++ .../common/VolumetricFog/VFogPreV.hlsl | 46 ++ .../common/VolumetricFog/VFogRefl.hlsl | 36 ++ .../shaders/common/VolumetricFog/VFogV.hlsl | 45 ++ .../common/VolumetricFog/gl/VFogP.glsl | 87 ++++ .../common/VolumetricFog/gl/VFogPreP.glsl | 37 ++ .../common/VolumetricFog/gl/VFogPreV.glsl | 42 ++ .../common/VolumetricFog/gl/VFogRefl.glsl | 33 ++ .../common/VolumetricFog/gl/VFogV.glsl | 38 ++ .../shaders/common/postFx/VolFogGlowP.hlsl | 74 +++ .../shaders/common/postFx/gl/VolFogGlowP.glsl | 67 +++ .../game/tools/classIcons/VolumetricFog.png | Bin 0 -> 3642 bytes .../worldEditor/gui/objectBuilderGui.ed.gui | 14 +- .../worldEditor/scripts/editors/creator.ed.cs | 1 + .../game/art/environment/FogMod_heavy.dds | Bin 0 -> 131200 bytes .../game/art/environment/FogMod_light.dds | Bin 0 -> 131200 bytes .../Full/game/art/environment/FogMod_med.dds | Bin 0 -> 131200 bytes .../Full/game/art/environment/Fog_Cube.DAE | 177 ++++++++ .../Full/game/art/environment/Fog_Cube.cs | 8 + .../art/environment/LightVolume_Sphere.DAE | 423 ++++++++++++++++++ .../art/environment/LightVolume_Sphere.cs | 8 + .../art/environment/LightVolume_Sphere.dts | Bin 0 -> 12883 bytes .../game/core/scripts/client/postFx/glow.cs | 76 ++++ .../postFx/postFxManager.gui.settings.cs | 1 + .../game/core/scripts/client/renderManager.cs | 2 + .../Full/game/core/scripts/client/shaders.cs | 36 ++ .../Full/game/scripts/server/VolumetricFog.cs | 106 +++++ .../Full/game/scripts/server/scriptExec.cs | 1 + .../shaders/common/VolumetricFog/VFogP.hlsl | 87 ++++ .../common/VolumetricFog/VFogPreP.hlsl | 39 ++ .../common/VolumetricFog/VFogPreV.hlsl | 46 ++ .../common/VolumetricFog/VFogRefl.hlsl | 37 ++ .../shaders/common/VolumetricFog/VFogV.hlsl | 45 ++ .../common/VolumetricFog/gl/VFogP.glsl | 87 ++++ .../common/VolumetricFog/gl/VFogPreP.glsl | 37 ++ .../common/VolumetricFog/gl/VFogPreV.glsl | 42 ++ .../common/VolumetricFog/gl/VFogRefl.glsl | 33 ++ .../common/VolumetricFog/gl/VFogV.glsl | 38 ++ .../shaders/common/postFx/VolFogGlowP.hlsl | 74 +++ .../shaders/common/postFx/gl/VolFogGlowP.glsl | 67 +++ .../game/tools/classIcons/VolumetricFog.png | Bin 0 -> 3642 bytes .../worldEditor/gui/objectBuilderGui.ed.gui | 14 +- .../worldEditor/scripts/editors/creator.ed.cs | 1 + 62 files changed, 2541 insertions(+), 6 deletions(-) create mode 100644 Templates/Empty/game/art/environment/FogMod_heavy.dds create mode 100644 Templates/Empty/game/art/environment/FogMod_light.dds create mode 100644 Templates/Empty/game/art/environment/FogMod_med.dds create mode 100644 Templates/Empty/game/art/environment/Fog_Cube.DAE create mode 100644 Templates/Empty/game/art/environment/Fog_Cube.cs create mode 100644 Templates/Empty/game/scripts/server/VolumetricFog.cs create mode 100644 Templates/Empty/game/shaders/common/VolumetricFog/VFogP.hlsl create mode 100644 Templates/Empty/game/shaders/common/VolumetricFog/VFogPreP.hlsl create mode 100644 Templates/Empty/game/shaders/common/VolumetricFog/VFogPreV.hlsl create mode 100644 Templates/Empty/game/shaders/common/VolumetricFog/VFogRefl.hlsl create mode 100644 Templates/Empty/game/shaders/common/VolumetricFog/VFogV.hlsl create mode 100644 Templates/Empty/game/shaders/common/VolumetricFog/gl/VFogP.glsl create mode 100644 Templates/Empty/game/shaders/common/VolumetricFog/gl/VFogPreP.glsl create mode 100644 Templates/Empty/game/shaders/common/VolumetricFog/gl/VFogPreV.glsl create mode 100644 Templates/Empty/game/shaders/common/VolumetricFog/gl/VFogRefl.glsl create mode 100644 Templates/Empty/game/shaders/common/VolumetricFog/gl/VFogV.glsl create mode 100644 Templates/Empty/game/shaders/common/postFx/VolFogGlowP.hlsl create mode 100644 Templates/Empty/game/shaders/common/postFx/gl/VolFogGlowP.glsl create mode 100644 Templates/Empty/game/tools/classIcons/VolumetricFog.png create mode 100644 Templates/Full/game/art/environment/FogMod_heavy.dds create mode 100644 Templates/Full/game/art/environment/FogMod_light.dds create mode 100644 Templates/Full/game/art/environment/FogMod_med.dds create mode 100644 Templates/Full/game/art/environment/Fog_Cube.DAE create mode 100644 Templates/Full/game/art/environment/Fog_Cube.cs create mode 100644 Templates/Full/game/art/environment/LightVolume_Sphere.DAE create mode 100644 Templates/Full/game/art/environment/LightVolume_Sphere.cs create mode 100644 Templates/Full/game/art/environment/LightVolume_Sphere.dts create mode 100644 Templates/Full/game/scripts/server/VolumetricFog.cs create mode 100644 Templates/Full/game/shaders/common/VolumetricFog/VFogP.hlsl create mode 100644 Templates/Full/game/shaders/common/VolumetricFog/VFogPreP.hlsl create mode 100644 Templates/Full/game/shaders/common/VolumetricFog/VFogPreV.hlsl create mode 100644 Templates/Full/game/shaders/common/VolumetricFog/VFogRefl.hlsl create mode 100644 Templates/Full/game/shaders/common/VolumetricFog/VFogV.hlsl create mode 100644 Templates/Full/game/shaders/common/VolumetricFog/gl/VFogP.glsl create mode 100644 Templates/Full/game/shaders/common/VolumetricFog/gl/VFogPreP.glsl create mode 100644 Templates/Full/game/shaders/common/VolumetricFog/gl/VFogPreV.glsl create mode 100644 Templates/Full/game/shaders/common/VolumetricFog/gl/VFogRefl.glsl create mode 100644 Templates/Full/game/shaders/common/VolumetricFog/gl/VFogV.glsl create mode 100644 Templates/Full/game/shaders/common/postFx/VolFogGlowP.hlsl create mode 100644 Templates/Full/game/shaders/common/postFx/gl/VolFogGlowP.glsl create mode 100644 Templates/Full/game/tools/classIcons/VolumetricFog.png diff --git a/Engine/source/gui/core/guiCanvas.cpp b/Engine/source/gui/core/guiCanvas.cpp index 84fe6e0c2..a3f2344f2 100644 --- a/Engine/source/gui/core/guiCanvas.cpp +++ b/Engine/source/gui/core/guiCanvas.cpp @@ -321,8 +321,11 @@ void GuiCanvas::setWindowTitle(const char *newTitle) mPlatformWindow->setCaption(newTitle); } +CanvasSizeChangeSignal GuiCanvas::smCanvasSizeChangeSignal; + void GuiCanvas::handleResize( WindowId did, S32 width, S32 height ) { + getCanvasSizeChangeSignal().trigger(this); if (Journal::IsPlaying() && mPlatformWindow) { mPlatformWindow->lockSize(false); diff --git a/Engine/source/gui/core/guiCanvas.h b/Engine/source/gui/core/guiCanvas.h index b1444ce32..0a2e44fa5 100644 --- a/Engine/source/gui/core/guiCanvas.h +++ b/Engine/source/gui/core/guiCanvas.h @@ -33,6 +33,10 @@ #include "platform/platformInput.h" #endif +#ifndef _SIGNAL_H_ +#include "core/util/tSignal.h" +#endif + #include "component/interfaces/IProcessInput.h" #include "windowManager/platformWindowMgr.h" #include "gfx/gfxFence.h" @@ -74,6 +78,8 @@ /// screen will be painted normally. If you are making an animated GuiControl /// you need to add your control to the dirty areas of the canvas. /// +class guiCanvas; +typedef Signal CanvasSizeChangeSignal; class GuiCanvas : public GuiControl, public IProcessInput { @@ -183,6 +189,8 @@ protected: virtual void setupFences(); void checkLockMouseMove( const GuiEvent& event ); + //Signal used to let others know this canvas has changed size. + static CanvasSizeChangeSignal smCanvasSizeChangeSignal; GuiControl *mMenuBarCtrl; @@ -200,6 +208,8 @@ public: static void initPersistFields(); + static CanvasSizeChangeSignal& getCanvasSizeChangeSignal() { return smCanvasSizeChangeSignal; } + /// @name Rendering methods /// /// @{ diff --git a/Engine/source/renderInstance/renderPassManager.cpp b/Engine/source/renderInstance/renderPassManager.cpp index 236cccd9b..68daed77e 100644 --- a/Engine/source/renderInstance/renderPassManager.cpp +++ b/Engine/source/renderInstance/renderPassManager.cpp @@ -53,6 +53,7 @@ const RenderInstType RenderPassManager::RIT_ObjectTranslucent("ObjectTranslucent const RenderInstType RenderPassManager::RIT_Decal("Decal"); const RenderInstType RenderPassManager::RIT_Water("Water"); const RenderInstType RenderPassManager::RIT_Foliage("Foliage"); +const RenderInstType RenderPassManager::RIT_VolumetricFog("ObjectVolumetricFog"); const RenderInstType RenderPassManager::RIT_Translucent("Translucent"); const RenderInstType RenderPassManager::RIT_Begin("Begin"); const RenderInstType RenderPassManager::RIT_Custom("Custom"); diff --git a/Engine/source/renderInstance/renderPassManager.h b/Engine/source/renderInstance/renderPassManager.h index b7f72b06c..b192fdb0e 100644 --- a/Engine/source/renderInstance/renderPassManager.h +++ b/Engine/source/renderInstance/renderPassManager.h @@ -110,6 +110,7 @@ public: static const RenderInstType RIT_Decal; static const RenderInstType RIT_Water; static const RenderInstType RIT_Foliage; + static const RenderInstType RIT_VolumetricFog; static const RenderInstType RIT_Translucent; static const RenderInstType RIT_Begin; static const RenderInstType RIT_Custom; diff --git a/Engine/source/renderInstance/renderTranslucentMgr.cpp b/Engine/source/renderInstance/renderTranslucentMgr.cpp index b755e12c9..7ad324a26 100644 --- a/Engine/source/renderInstance/renderTranslucentMgr.cpp +++ b/Engine/source/renderInstance/renderTranslucentMgr.cpp @@ -51,6 +51,7 @@ RenderTranslucentMgr::RenderTranslucentMgr() { notifyType( RenderPassManager::RIT_ObjectTranslucent ); notifyType( RenderPassManager::RIT_Particle ); + notifyType( RenderPassManager::RIT_VolumetricFog); } RenderTranslucentMgr::~RenderTranslucentMgr() @@ -187,6 +188,15 @@ void RenderTranslucentMgr::render( SceneRenderState *state ) j++; continue; } + else if (baseRI->type == RenderPassManager::RIT_VolumetricFog) + { + ObjectRenderInst* objRI = static_cast(baseRI); + objRI->renderDelegate(objRI, state, NULL); + lastVB = NULL; + lastPB = NULL; + j++; + continue; + } else if ( baseRI->type == RenderPassManager::RIT_Particle ) { ParticleRenderInst *ri = static_cast(baseRI); diff --git a/Engine/source/windowManager/platformWindow.cpp b/Engine/source/windowManager/platformWindow.cpp index 8a7ad65fb..b11ac0bb7 100644 --- a/Engine/source/windowManager/platformWindow.cpp +++ b/Engine/source/windowManager/platformWindow.cpp @@ -22,7 +22,7 @@ #include "windowManager/platformWindow.h" - +ScreenResChangeSignal PlatformWindow::smScreenResChangeSignal; //----------------------------------------------------------------------------- void PlatformWindow::setFullscreen( const bool fullscreen ) @@ -48,3 +48,8 @@ bool PlatformWindow::shouldNotTranslate( U32 modifiers, U32 keyCode ) const else return false; } +void PlatformWindow::setVideoMode(const GFXVideoMode &mode) +{ + _setVideoMode(mode); + getScreenResChangeSignal().trigger(this, true); +} \ No newline at end of file diff --git a/Engine/source/windowManager/platformWindow.h b/Engine/source/windowManager/platformWindow.h index 6db7ce423..df1b51595 100644 --- a/Engine/source/windowManager/platformWindow.h +++ b/Engine/source/windowManager/platformWindow.h @@ -28,6 +28,9 @@ #include "core/util/safeDelete.h" #include "windowManager/platformCursorController.h" #include "windowManager/windowInputGenerator.h" +#ifndef _SIGNAL_H_ //Volumetric Fog +#include "core/util/tSignal.h" +#endif //forward decl's class PlatformWindowManager; @@ -35,7 +38,7 @@ class GFXDevice; struct GFXVideoMode; class GFXWindowTarget; class IProcessInput; - +typedef Signal ScreenResChangeSignal; /// Abstract representation of a native OS window. /// /// Every windowing system has its own representations and conventions as @@ -110,7 +113,7 @@ protected: // This controller maps window input (Mouse/Keyboard) to a generic input consumer mWindowInputGenerator = new WindowInputGenerator( this ); } - + static ScreenResChangeSignal smScreenResChangeSignal; public: /// To get rid of a window, just delete it. Make sure the GFXDevice is @@ -158,7 +161,7 @@ public: virtual GFXWindowTarget *getGFXTarget()=0; /// Set the video mode for this window. - virtual void setVideoMode(const GFXVideoMode &mode)=0; + virtual void setVideoMode(const GFXVideoMode &mode); /// Get our current video mode - if the window has been resized, it will /// reflect this. @@ -497,6 +500,7 @@ public: IdleEvent idleEvent; /// @} + static ScreenResChangeSignal& getScreenResChangeSignal() { return smScreenResChangeSignal; } /// Get the platform specific object needed to create or attach an accelerated /// graohics drawing context on or to the window @@ -507,6 +511,7 @@ public: virtual void* getPlatformDrawable() const = 0; protected: virtual void _setFullscreen(const bool fullScreen) {}; + virtual void _setVideoMode(const GFXVideoMode &mode) {}; }; #endif diff --git a/Engine/source/windowManager/win32/win32Window.cpp b/Engine/source/windowManager/win32/win32Window.cpp index 88be80cf1..5c9b8ed11 100644 --- a/Engine/source/windowManager/win32/win32Window.cpp +++ b/Engine/source/windowManager/win32/win32Window.cpp @@ -287,6 +287,8 @@ void Win32Window::setVideoMode( const GFXVideoMode &mode ) mOwningManager->raiseCurtain(); SetForegroundWindow(getHWND()); + + getScreenResChangeSignal().trigger(this, true); } bool Win32Window::clearFullscreen() diff --git a/Templates/Empty/game/art/environment/FogMod_heavy.dds b/Templates/Empty/game/art/environment/FogMod_heavy.dds new file mode 100644 index 0000000000000000000000000000000000000000..197dd43327352716bc08bd069209499bf6b5a8f1 GIT binary patch literal 131200 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 n;hV-7aDAP}zDS literal 0 HcmV?d00001 diff --git a/Templates/Empty/game/art/environment/FogMod_light.dds b/Templates/Empty/game/art/environment/FogMod_light.dds new file mode 100644 index 0000000000000000000000000000000000000000..d726795fa1de24d57dc54259c7c57f984e3d6704 GIT binary patch literal 131200 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%0DDkgQ;%|w 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൏u$o87pO_E*IzlWcQhKlOkZMNW<&fqwN}9Y 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 bRO-0LHU0XoSI+pJx4tj`BkL3><($#K7P0$f literal 0 HcmV?d00001 diff --git a/Templates/Empty/game/art/environment/Fog_Cube.DAE b/Templates/Empty/game/art/environment/Fog_Cube.DAE new file mode 100644 index 000000000..34cad9f48 --- /dev/null +++ b/Templates/Empty/game/art/environment/Fog_Cube.DAE @@ -0,0 +1,177 @@ + + + + + Richard + OpenCOLLADA for 3ds Max; Version: 1.4.1; Revision: exported; Platform: x64; Configuration: Release_Max2011_static + file:///G:/Documents%20and%20Settings/Richard/Mijn%20documenten/3dsmax/scenes/FogVolumes.max + + 2014-08-16T10:10:23 + 2014-08-16T10:10:23 + + Z_UP + + + + + + + + 0 0 0 1 + + + 0.588 0.588 0.588 1 + + + 0.588 0.588 0.588 1 + + + 0.9 0.9 0.9 1 + + + 0 + + + 0 0 0 1 + + + 1 1 1 1 + + + 1 + + + + + + + + 0 + 0 + 0 + 1.5 + 0 + 3 + 1 + 0 + + + 1 + 1 + 0 + 0.1 + 0 + + + + + + + + + + + + + + + -0.85 -1 -0.85 0.85 -0.85 -1 -1 0.85 -0.85 0.85 0.85 -1 -0.85 -1 0.85 1 -0.85 0.85 -1 0.85 0.85 0.85 1 0.85 -1 -0.85 -0.85 -0.85 -0.85 -1 1 -0.85 -0.85 0.85 -1 -0.85 -0.85 1 -0.85 -0.85 0.85 -1 0.85 1 -0.85 1 0.85 -0.85 -0.85 -0.85 1 -1 -0.85 0.85 0.85 -0.85 1 0.85 -1 0.85 -0.85 0.85 1 -0.85 1 0.85 0.85 0.85 1 1 0.85 0.85 + + + + + + + + + + -0.341586 -0.341586 -0.8755786 -0.341586 0.341586 -0.8755788 0.341586 0.341586 -0.8755788 0.341586 -0.341586 -0.8755788 -0.341586 -0.341586 0.8755786 0.341586 -0.341586 0.8755788 0.341586 0.341586 0.8755788 -0.341586 0.341586 0.8755788 -0.341586 -0.8755786 -0.341586 0.341586 -0.8755788 -0.341586 0.341586 -0.8755786 0.341586 -0.341586 -0.8755788 0.341586 0.8755786 -0.341586 -0.341586 0.8755788 0.341586 -0.341586 0.8755786 0.341586 0.341586 0.8755788 -0.341586 0.341586 0.341586 0.8755786 -0.341586 -0.341586 0.8755788 -0.341586 -0.341586 0.8755786 0.341586 0.341586 0.8755788 0.341586 -0.8755786 0.341586 -0.341586 -0.8755788 -0.341586 -0.341586 -0.8755786 -0.341586 0.341586 -0.8755788 0.341586 0.341586 + + + + + + + + + + 0 0 0 1 0 0 0 1 0 1 1 0 0 0 0 1 0 0 0 1 0 1 1 0 0 0 0 1 0 0 0 1 0 1 1 0 0.07542458 0.07542461 4.99755e-4 0.07542479 0.07542461 4.99547e-4 0.07542455 0.07542461 4.99755e-4 0.07542461 0.9245752 4.99547e-4 0.07542458 0.9245754 4.99755e-4 0.07542458 0.9245754 0.9995003 0.07542455 0.9245754 4.99755e-4 0.07542455 0.9245754 0.9995003 0.9245752 0.07542461 4.99576e-4 0.9245754 0.07542479 4.99547e-4 0.07542458 0.07542461 0.9995003 0.9245752 0.07542461 4.99576e-4 0.9245752 0.07542461 0.9995004 0.9245752 0.9245754 4.99547e-4 0.07542455 0.07542461 0.9995003 0.9245752 0.07542461 0.9995004 0.07542461 0.07542479 0.9995005 0.9245752 0.9245754 4.99576e-4 0.9245752 0.07542461 0.9995005 0.9245752 0.9245754 4.99576e-4 0.07542479 0.9245754 0.9995005 0.9245752 0.9245754 0.9995004 0.9245754 0.9245752 0.9995005 0.9245752 0.9245754 0.9995004 0.9995003 0.07542461 0.07542458 0.9245752 4.99547e-4 0.07542461 0.9245752 4.99547e-4 0.07542461 0.9995003 0.07542461 0.07542458 0.9995003 0.07542461 0.9245754 0.9245752 4.99547e-4 0.9245754 0.9245752 4.99547e-4 0.9245754 0.9995003 0.07542461 0.9245754 0.9995003 0.9245754 0.07542458 0.9245752 0.9995005 0.07542461 0.9995003 0.9245754 0.07542458 0.9245752 0.9995005 0.07542461 0.9995003 0.9245754 0.9245754 0.9245752 0.9995005 0.9245754 0.9995003 0.9245754 0.9245754 0.9245752 0.9995005 0.9245754 0.9995004 0.07542482 0.07542461 0.9995003 0.9245754 0.07542461 0.9245752 0.9995004 0.07542461 0.07542455 0.9995003 0.07542461 4.99606e-4 0.9245752 0.07542461 4.99725e-4 0.07542458 0.07542461 0.07542479 4.99576e-4 0.07542461 0.9245754 4.99755e-4 0.07542461 0.07542458 4.99755e-4 0.9245754 0.9245752 4.99576e-4 0.9245754 0.9995003 0.07542458 0.9245754 0.9995004 0.9245752 0.9245754 0.9245754 0.9995003 0.9245754 0.07542482 0.9995004 0.9245754 4.99755e-4 0.9245754 0.9245754 4.99576e-4 0.07542482 0.9245754 0.9995003 0.07542461 0.07542458 0.9995003 0.9245754 0.07542458 0.9995003 0.9245754 0.07542458 0.9995003 0.07542461 0.07542458 0.9995003 0.07542461 0.9245754 0.9995003 0.9245754 0.9245754 0.9995003 0.07542461 0.9245754 0.9995003 0.9245754 0.9245754 + + + + + + + + + + -0.8644259 0.01841655 0.3300502 -0.8715108 -0.05526615 0.3184382 -0.8644259 0.01841664 -0.3300501 -0.8715108 -0.05526611 -0.3184382 0.8738725 -0.06754867 0.3145678 0.8597026 -0.006149054 -0.3377912 0.8738725 -0.06754874 -0.3145678 0.8597026 -0.006148929 0.3377911 0.883319 -0.2990854 -0.116681 0.8478944 0.3571441 -0.06756432 0.8597026 0.3377913 0.00614921 0.883319 -0.2990854 0.116681 0.2990854 0.883319 -0.116681 -0.3571441 0.8478944 -0.06756432 -0.3377913 0.8597026 0.00614921 0.2990854 0.883319 0.116681 -0.883319 0.2990854 -0.116681 -0.8478944 -0.3571441 -0.06756432 -0.8597026 -0.3377913 0.00614921 -0.883319 0.2990854 0.116681 -0.2990854 -0.883319 -0.116681 0.3571441 -0.8478944 -0.06756432 0.3377913 -0.8597026 0.00614921 -0.2990854 -0.883319 0.116681 0.8360862 -0.3764972 0.1289794 0.7071068 -0.7071068 0 0.7071068 0.7071068 0 0.3764972 0.8360862 0.1289794 -0.3764972 -0.8360862 0.1289794 -0.7071068 -0.7071068 0 -0.7071068 0.7071068 0 -0.8360862 0.3764972 0.1289794 0.8360862 -0.3764971 -0.1289794 0.7071068 -0.7071068 0 0.3764971 0.8360862 -0.1289794 0.7071068 0.7071068 0 -0.3764971 -0.8360862 -0.1289794 -0.7071068 -0.7071068 0 -0.8360862 0.3764971 -0.1289794 -0.7071068 0.7071068 0 -0.376497 0.1289792 0.8360862 -0.3764973 -0.1289798 0.8360861 -0.8833191 -0.2990855 0.1166808 -0.883319 0.2990853 -0.1166812 -0.3764971 0.1289794 -0.8360862 -0.3764972 -0.1289795 -0.8360862 -0.8833191 -0.2990855 -0.1166807 -0.883319 0.2990853 0.1166812 0.883319 -0.2990853 0.1166812 0.8833191 0.2990855 -0.1166807 0.3764971 0.1289797 -0.8360862 0.3764971 -0.1289793 -0.8360862 0.883319 -0.2990853 -0.1166811 0.8833191 0.2990855 0.1166808 0.3764972 0.1289799 0.8360861 0.3764971 -0.128979 0.8360862 0.3764972 0.8360862 0.1289794 0.3764971 0.8360862 -0.1289794 0.8360862 -0.3764971 -0.1289794 0.8360862 -0.3764972 0.1289794 -0.8360862 0.3764972 0.1289794 -0.8360862 0.3764971 -0.1289794 -0.3764972 -0.8360862 0.1289794 -0.3764971 -0.8360862 -0.1289794 + + + + + + + + + + 0.1043954 -0.9396398 0.3258505 -0.06496345 -0.9379679 -0.3405817 0.1043953 -0.9396398 -0.3258505 -0.06496349 -0.937968 0.3405817 0.05187585 -0.9370471 -0.3453283 -0.1307439 -0.939827 -0.3156443 0.05187577 -0.9370471 0.3453283 -0.1307438 -0.939827 0.3156443 0 0.3634471 -0.9316148 -0.196368 0.2889368 -0.9369926 0.1307441 -0.3156442 -0.939827 0 -0.3634471 -0.9316148 -0.3634471 0 -0.9316148 -0.2889368 -0.196368 -0.9369926 0.3156442 0.1307441 -0.939827 0.3634471 0 -0.9316148 0 -0.3634471 -0.9316148 0.196368 -0.2889368 -0.9369926 -0.1307441 0.3156442 -0.939827 0 0.3634471 -0.9316148 0.3634471 0 -0.9316148 0.2889368 0.196368 -0.9369926 -0.3156442 -0.1307441 -0.939827 -0.3634471 0 -0.9316148 0.2608475 0.2608475 -0.9294714 0.6191276 0.6191276 -0.4830755 -0.6191276 0.6191276 -0.4830755 -0.2608475 0.2608475 -0.9294714 0.2608475 -0.2608475 -0.9294714 0.6191276 -0.6191276 -0.4830755 -0.6191276 -0.6191276 -0.4830755 -0.2608475 -0.2608475 -0.9294714 -0.2608475 -0.2608475 -0.9294714 -0.6191276 -0.6191276 -0.4830755 0.2608475 -0.2608475 -0.9294714 0.6191276 -0.6191276 -0.4830755 -0.2608475 0.2608475 -0.9294714 -0.6191276 0.6191276 -0.4830755 0.2608475 0.2608475 -0.9294714 0.6191276 0.6191276 -0.4830755 0.2608476 -0.9294715 0.2608473 -0.2608474 -0.9294714 -0.2608479 1.81809e-7 -0.363447 -0.9316149 2.27262e-7 -0.3634472 -0.9316148 0.2608475 -0.9294714 -0.2608475 -0.2608475 -0.9294714 0.2608477 2.72714e-7 -0.363447 0.9316149 2.72714e-7 -0.3634472 0.9316148 2.72714e-7 -0.3634472 -0.9316148 2.72714e-7 -0.363447 -0.9316149 -0.2608474 -0.9294714 -0.2608478 0.2608476 -0.9294714 0.2608474 1.81809e-7 -0.3634472 0.9316148 2.27262e-7 -0.3634471 0.9316149 -0.2608473 -0.9294714 0.260848 0.2608477 -0.9294715 -0.2608472 -0.2608475 0.2608475 -0.9294714 0.2608475 -0.2608475 -0.9294714 -0.2608475 -0.2608475 -0.9294714 0.2608475 0.2608475 -0.9294714 -0.2608475 -0.2608475 -0.9294714 0.2608475 0.2608475 -0.9294714 0.2608475 -0.2608475 -0.9294714 -0.2608475 0.2608475 -0.9294714 + + + + + + + + + + + + + + + + + +

9 0 21 0 13 1 25 1 3 2 15 2 3 2 15 2 1 3 13 3 9 0 21 0 16 4 28 4 18 5 30 5 22 6 34 6 22 6 34 6 20 7 32 7 16 4 28 4 0 8 12 8 11 9 23 9 19 10 31 10 19 10 31 10 4 11 16 11 0 8 12 8 10 12 22 12 15 13 27 13 23 14 35 14 23 14 35 14 5 15 17 15 10 12 22 12 14 16 26 16 12 17 24 17 21 18 33 18 21 18 33 18 7 19 19 19 14 16 26 16 2 20 14 20 8 21 20 21 17 22 29 22 17 22 29 22 6 23 18 23 2 20 14 20 0 8 36 24 8 21 20 21 9 0 37 25 1 3 38 26 10 12 39 27 11 9 23 9 2 20 40 28 12 17 24 17 13 1 41 29 3 2 42 30 14 16 43 31 15 13 27 13 4 11 44 32 16 4 45 33 17 22 29 22 5 15 46 34 18 5 47 35 19 10 31 10 6 23 48 36 20 7 49 37 21 18 33 18 7 19 50 38 22 6 51 39 23 14 35 14 9 0 21 0 8 21 52 40 2 20 53 41 2 20 53 41 13 1 25 1 9 0 21 0 13 1 25 1 12 17 54 42 14 16 55 43 14 16 55 43 3 2 15 2 13 1 25 1 3 2 15 2 15 13 56 44 10 12 57 45 10 12 57 45 1 3 13 3 3 2 15 2 1 3 13 3 11 9 58 46 0 8 59 47 0 8 59 47 9 0 21 0 1 3 13 3 16 4 28 4 4 11 60 48 19 10 61 49 19 10 61 49 18 5 30 5 16 4 28 4 18 5 30 5 5 15 62 50 23 14 63 51 23 14 63 51 22 6 34 6 18 5 30 5 22 6 34 6 7 19 64 52 21 18 65 53 21 18 65 53 20 7 32 7 22 6 34 6 20 7 32 7 6 23 66 54 17 22 67 55 17 22 67 55 16 4 28 4 20 7 32 7 11 9 23 9 10 12 68 56 5 15 69 57 5 15 69 57 19 10 31 10 11 9 23 9 4 11 70 58 17 22 29 22 8 21 20 21 8 21 20 21 0 8 71 59 4 11 70 58 15 13 27 13 14 16 72 60 7 19 73 61 7 19 73 61 23 14 35 14 15 13 27 13 12 17 24 17 2 20 74 62 6 23 75 63 6 23 75 63 21 18 33 18 12 17 24 17

+
+
+
+
+ + + + + 0 0 0 + + + + + + + + + + + + + + + + + + + + 1 + 1 + 1 + 1 + + + + + + + + +
\ No newline at end of file diff --git a/Templates/Empty/game/art/environment/Fog_Cube.cs b/Templates/Empty/game/art/environment/Fog_Cube.cs new file mode 100644 index 000000000..3c686032c --- /dev/null +++ b/Templates/Empty/game/art/environment/Fog_Cube.cs @@ -0,0 +1,8 @@ + +singleton TSShapeConstructor(Fog_CubeDAE) +{ + baseShape = "./Fog_Cube.DAE"; + lodType = "TrailingNumber"; + neverImport = "env*"; + loadLights = "0"; +}; diff --git a/Templates/Empty/game/core/scripts/client/postFx/glow.cs b/Templates/Empty/game/core/scripts/client/postFx/glow.cs index 3cc946b04..78c46e56d 100644 --- a/Templates/Empty/game/core/scripts/client/postFx/glow.cs +++ b/Templates/Empty/game/core/scripts/client/postFx/glow.cs @@ -106,3 +106,79 @@ singleton PostEffect( GlowPostFx ) target = "$backBuffer"; }; }; + +singleton ShaderData( PFX_VolFogGlowBlurVertShader ) +{ + DXVertexShaderFile = "shaders/common/postFx/glowBlurV.hlsl"; + DXPixelShaderFile = "shaders/common/postFx/VolFogGlowP.hlsl"; + + OGLVertexShaderFile = "shaders/common/postFx/gl/glowBlurV.glsl"; + OGLPixelShaderFile = "shaders/common/postFx/gl/VolFogGlowP.glsl"; + + defines = "BLUR_DIR=float2(0.0,1.0)"; + samplerNames[0] = "$diffuseMap"; + pixVersion = 2.0; +}; +singleton ShaderData( PFX_VolFogGlowBlurHorzShader : PFX_VolFogGlowBlurVertShader ) +{ + DXVertexShaderFile = "shaders/common/postFx/glowBlurV.hlsl"; + DXPixelShaderFile = "shaders/common/postFx/VolFogGlowP.hlsl"; + + OGLVertexShaderFile = "shaders/common/postFx/gl/glowBlurV.glsl"; + OGLPixelShaderFile = "shaders/common/postFx/gl/VolFogGlowP.glsl"; + + defines = "BLUR_DIR=float2(1.0,0.0)"; +}; + +$VolFogGlowPostFx::glowStrength = 0.3; + +singleton PostEffect( VolFogGlowPostFx ) +{ + // Do not allow the glow effect to work in reflection + // passes by default so we don't do the extra drawing. + allowReflectPass = false; + renderTime = "PFXAfterBin"; + renderBin = "FogBin"; + renderPriority = 1; + // First we down sample the glow buffer. + shader = PFX_PassthruShader; + stateBlock = PFX_DefaultStateBlock; + texture[0] = "$backbuffer"; + target = "$outTex"; + targetScale = "0.5 0.5"; + isEnabled = true; + // Blur vertically + new PostEffect() + { + shader = PFX_VolFogGlowBlurVertShader; + stateBlock = PFX_DefaultStateBlock; + internalName = "vert"; + texture[0] = "$inTex"; + target = "$outTex"; + }; + // Blur horizontally + new PostEffect() + { + shader = PFX_VolFogGlowBlurHorzShader; + stateBlock = PFX_DefaultStateBlock; + internalName = "hor"; + texture[0] = "$inTex"; + target = "$outTex"; + }; + // Upsample and combine with the back buffer. + new PostEffect() + { + shader = PFX_PassthruShader; + stateBlock = PFX_GlowCombineStateBlock; + texture[0] = "$inTex"; + target = "$backBuffer"; + }; +}; + +function VolFogGlowPostFx::setShaderConsts( %this ) +{ + %vp=%this-->vert; + %vp.setShaderConst( "$strength", $VolFogGlowPostFx::glowStrength ); + %vp=%this-->hor; + %vp.setShaderConst( "$strength", $VolFogGlowPostFx::glowStrength ); +} \ No newline at end of file diff --git a/Templates/Empty/game/core/scripts/client/postFx/postFxManager.gui.settings.cs b/Templates/Empty/game/core/scripts/client/postFx/postFxManager.gui.settings.cs index d30d2314b..77d664f41 100644 --- a/Templates/Empty/game/core/scripts/client/postFx/postFxManager.gui.settings.cs +++ b/Templates/Empty/game/core/scripts/client/postFx/postFxManager.gui.settings.cs @@ -70,6 +70,7 @@ function PostFXManager::settingsSetEnabled(%this, %bEnablePostFX) postVerbose("% - PostFX Manager - PostFX disabled"); } + VolFogGlowPostFx.disable(); } function PostFXManager::settingsEffectSetEnabled(%this, %sName, %bEnable) diff --git a/Templates/Empty/game/core/scripts/client/renderManager.cs b/Templates/Empty/game/core/scripts/client/renderManager.cs index dcd1628fe..5734bbce6 100644 --- a/Templates/Empty/game/core/scripts/client/renderManager.cs +++ b/Templates/Empty/game/core/scripts/client/renderManager.cs @@ -75,6 +75,8 @@ function initRenderManager() DiffuseRenderPassManager.addManager( new RenderParticleMgr() { renderOrder = 1.35; processAddOrder = 1.35; } ); DiffuseRenderPassManager.addManager( new RenderTranslucentMgr() { renderOrder = 1.4; processAddOrder = 1.4; } ); + DiffuseRenderPassManager.addManager(new RenderObjectMgr(FogBin){ bintype = "ObjectVolumetricFog"; renderOrder = 1.45; processAddOrder = 1.45; } ); + // Note that the GlowPostFx is triggered after this bin. DiffuseRenderPassManager.addManager( new RenderGlowMgr(GlowBin) { renderOrder = 1.5; processAddOrder = 1.5; } ); diff --git a/Templates/Empty/game/scripts/server/VolumetricFog.cs b/Templates/Empty/game/scripts/server/VolumetricFog.cs new file mode 100644 index 000000000..53e03adf3 --- /dev/null +++ b/Templates/Empty/game/scripts/server/VolumetricFog.cs @@ -0,0 +1,106 @@ +//----------------------------------------------------------------------------- +// Copyright (c) 2012 GarageGames, LLC +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. +//----------------------------------------------------------------------------- + +function VolumetricFog::onEnterFog(%this,%obj) +{ + // This method is called whenever the control object (Camera or Player) + // %obj enters the fog area. + + // echo("Control Object " @ %obj @ " enters fog " @ %this); +} + +function VolumetricFog::onLeaveFog(%this,%obj) +{ + // This method is called whenever the control object (Camera or Player) + // %obj leaves the fog area. + + // echo("Control Object " @ %obj @ " left fog " @ %this); +} + +function VolumetricFog::Dissolve(%this,%speed,%delete) +{ + // This method dissolves the fog at speed milliseconds + %this.isBuilding = true; + if (%this.FogDensity > 0) + { + %this.setFogDensity(%this.FogDensity - 0.005); + %this.schedule(%speed,Dissolve,%speed,%delete); + } + else + { + %this.isBuilding = false; + %this.SetFogDensity(0.0); + if (%delete !$= "" && %delete !$="0" && %delete !$="false") + %this.schedule(250,delete); + } +} + +function VolumetricFog::Thicken(%this,%speed, %end_density) +{ + // This method thickens the fog at speed milliseconds to a density of %end_density + + %this.isBuilding = true; + if (%this.FogDensity + 0.005 < %end_density) + { + %this.setFogDensity(%this.FogDensity + 0.005); + %this.schedule(%speed,Thicken,%speed, %end_density); + } + else + { + %this.setFogDensity(%end_density); + %this.isBuilding = false; + } +} + +function GenerateFog(%pos,%scale,%color,%density) +{ + // This function can be used to generate some fog caused by massive gunfire etc. + // Change shape and modulation data to your likings. + + %fog=new VolumetricFog() { + shapeName = "art/environment/Fog_Sphere.dts"; + fogColor = %color; + fogDensity = "0.0"; + ignoreWater = "0"; + MinSize = "250"; + FadeSize = "750"; + texture = "art/environment/FogMod_heavy.dds"; + tiles = "1"; + modStrength = "0.2"; + PrimSpeed = "-0.01 0.04"; + SecSpeed = "0.02 0.02"; + position = %pos; + rotation = "0 0 1 20.354"; + scale = %scale; + canSave = "1"; + canSaveDynamicFields = "1"; + }; + + if (isObject(%fog)) + { + MissionCleanup.add(%fog); + + %fog.Thicken(500,%density); + } + + return %fog; +} \ No newline at end of file diff --git a/Templates/Empty/game/scripts/server/scriptExec.cs b/Templates/Empty/game/scripts/server/scriptExec.cs index f2f2d1f58..77a5d8d27 100644 --- a/Templates/Empty/game/scripts/server/scriptExec.cs +++ b/Templates/Empty/game/scripts/server/scriptExec.cs @@ -22,3 +22,4 @@ // Load up all scripts. This function is called when // a server is constructed. +exec("./VolumetricFog.cs"); \ No newline at end of file diff --git a/Templates/Empty/game/shaders/common/VolumetricFog/VFogP.hlsl b/Templates/Empty/game/shaders/common/VolumetricFog/VFogP.hlsl new file mode 100644 index 000000000..aaadbf479 --- /dev/null +++ b/Templates/Empty/game/shaders/common/VolumetricFog/VFogP.hlsl @@ -0,0 +1,87 @@ +//----------------------------------------------------------------------------- +// Copyright (c) 2012 GarageGames, LLC +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. +//----------------------------------------------------------------------------- + +// Volumetric Fog final pixel shader V2.00 + +#include "shadergen:/autogenConditioners.h" +#include "../torque.hlsl" + +uniform sampler2D prepassTex : register(S0); +uniform sampler2D depthBuffer : register(S1); +uniform sampler2D frontBuffer : register(S2); +uniform sampler2D density : register(S3); + +uniform float accumTime; +uniform float4 fogColor; +uniform float fogDensity; +uniform float preBias; +uniform float textured; +uniform float modstrength; +uniform float4 modspeed;//xy speed layer 1, zw speed layer 2 +uniform float2 viewpoint; +uniform float2 texscale; +uniform float3 ambientColor; +uniform float numtiles; +uniform float fadesize; +uniform float2 PixelSize; + +struct ConnectData +{ + float4 hpos : POSITION; + float4 htpos : TEXCOORD0; + float2 uv0 : TEXCOORD1; +}; + +float4 main( ConnectData IN ) : COLOR0 +{ + float2 uvscreen=((IN.htpos.xy/IN.htpos.w) + 1.0 ) / 2.0; + uvscreen.y = 1.0 - uvscreen.y; + + float obj_test = prepassUncondition( prepassTex, uvscreen).w * preBias; + float depth = tex2D(depthBuffer,uvscreen).r; + float front = tex2D(frontBuffer,uvscreen).r; + + if (depth <= front) + return float4(0,0,0,0); + else if ( obj_test < depth ) + depth = obj_test; + if ( front >= 0.0) + depth -= front; + + float diff = 1.0; + float3 col = fogColor.rgb; + if (textured != 0.0) + { + float2 offset = viewpoint + ((-0.5 + (texscale * uvscreen)) * numtiles); + + float2 mod1 = tex2D(density,(offset + (modspeed.xy*accumTime))).rg; + float2 mod2= tex2D(density,(offset + (modspeed.zw*accumTime))).rg; + diff = (mod2.r + mod1.r) * modstrength; + col *= (2.0 - ((mod1.g + mod2.g) * fadesize))/2.0; + } + + col *= ambientColor; + + float4 resultColor = float4(col, 1.0 - saturate(exp(-fogDensity * depth * diff * fadesize))); + + return hdrEncode(resultColor); +} diff --git a/Templates/Empty/game/shaders/common/VolumetricFog/VFogPreP.hlsl b/Templates/Empty/game/shaders/common/VolumetricFog/VFogPreP.hlsl new file mode 100644 index 000000000..bb06f5f7c --- /dev/null +++ b/Templates/Empty/game/shaders/common/VolumetricFog/VFogPreP.hlsl @@ -0,0 +1,39 @@ +//----------------------------------------------------------------------------- +// Copyright (c) 2012 GarageGames, LLC +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. +//----------------------------------------------------------------------------- + +// Volumetric Fog prepass pixel shader V1.00 + +struct ConnectData +{ + float4 hpos : POSITION; + float4 pos : TEXCOORD0; +}; + +float4 main( ConnectData IN ) : COLOR0 +{ + float OUT; + + clip( IN.pos.w ); + OUT = IN.pos.w; + + return float4(OUT,0,0,1); +} diff --git a/Templates/Empty/game/shaders/common/VolumetricFog/VFogPreV.hlsl b/Templates/Empty/game/shaders/common/VolumetricFog/VFogPreV.hlsl new file mode 100644 index 000000000..2d13cdf01 --- /dev/null +++ b/Templates/Empty/game/shaders/common/VolumetricFog/VFogPreV.hlsl @@ -0,0 +1,46 @@ +//----------------------------------------------------------------------------- +// Copyright (c) 2012 GarageGames, LLC +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. +//----------------------------------------------------------------------------- + +// Volumetric Fog prepass vertex shader V1.00 + +#include "shaders/common/hlslstructs.h" + +struct ConnectData +{ + float4 hpos : POSITION; + float4 pos : TEXCOORD0; +}; + +uniform float4x4 modelView; + +ConnectData main( VertexIn_P IN) +{ + ConnectData OUT; + + float4 inPos = IN.pos; + inPos.w = 1.0; + + OUT.hpos = mul( modelView, inPos ); + OUT.pos = OUT.hpos; + + return OUT; +} diff --git a/Templates/Empty/game/shaders/common/VolumetricFog/VFogRefl.hlsl b/Templates/Empty/game/shaders/common/VolumetricFog/VFogRefl.hlsl new file mode 100644 index 000000000..87226a1ac --- /dev/null +++ b/Templates/Empty/game/shaders/common/VolumetricFog/VFogRefl.hlsl @@ -0,0 +1,36 @@ +//----------------------------------------------------------------------------- +// Copyright (c) 2012 GarageGames, LLC +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. +//----------------------------------------------------------------------------- + +uniform float4 fogColor; +uniform float fogDensity; +uniform float reflStrength; + +struct ConnectData +{ + float4 hpos : POSITION; + float4 pos : TEXCOORD0; +}; + +float4 main( ConnectData IN ) : COLOR0 +{ + return float4(fogColor.rgb,saturate(fogDensity*reflStrength)); +} diff --git a/Templates/Empty/game/shaders/common/VolumetricFog/VFogV.hlsl b/Templates/Empty/game/shaders/common/VolumetricFog/VFogV.hlsl new file mode 100644 index 000000000..7f86802b5 --- /dev/null +++ b/Templates/Empty/game/shaders/common/VolumetricFog/VFogV.hlsl @@ -0,0 +1,45 @@ +//----------------------------------------------------------------------------- +// Copyright (c) 2012 GarageGames, LLC +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. +//----------------------------------------------------------------------------- + +// Volumetric Fog final vertex shader V1.00 + +#include "shaders/common/hlslstructs.h" + +struct ConnectData +{ + float4 hpos : POSITION; + float4 htpos : TEXCOORD0; + float2 uv0 : TEXCOORD1; +}; + +uniform float4x4 modelView; + +ConnectData main( VertexIn_PNT IN) +{ + ConnectData OUT; + + OUT.hpos = mul(modelView, IN.pos); + OUT.htpos = OUT.hpos; + OUT.uv0 = IN.uv0; + + return OUT; +} diff --git a/Templates/Empty/game/shaders/common/VolumetricFog/gl/VFogP.glsl b/Templates/Empty/game/shaders/common/VolumetricFog/gl/VFogP.glsl new file mode 100644 index 000000000..7895d9e2d --- /dev/null +++ b/Templates/Empty/game/shaders/common/VolumetricFog/gl/VFogP.glsl @@ -0,0 +1,87 @@ +//----------------------------------------------------------------------------- +// Copyright (c) 2012 GarageGames, LLC +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. +//----------------------------------------------------------------------------- + +#include "../../gl/hlslCompat.glsl" +#include "shadergen:/autogenConditioners.h" +#include "../../gl/torque.glsl" + +uniform sampler2D prepassTex; +uniform sampler2D depthBuffer; +uniform sampler2D frontBuffer; +uniform sampler2D density; + +uniform float accumTime; +uniform vec4 fogColor; +uniform float fogDensity; +uniform float preBias; +uniform float textured; +uniform float modstrength; +uniform vec4 modspeed;//xy speed layer 1, zw speed layer 2 +uniform vec2 viewpoint; +uniform vec2 texscale; +uniform vec3 ambientColor; +uniform float numtiles; +uniform float fadesize; +uniform vec2 PixelSize; + +in vec4 _hpos; +#define IN_hpos _hpos +out vec4 OUT_col; + +void main() +{ + vec2 uvscreen=((IN_hpos.xy/IN_hpos.w) + 1.0 ) / 2.0; + uvscreen.y = 1.0 - uvscreen.y; + + float obj_test = prepassUncondition( prepassTex, uvscreen).w * preBias; + float depth = tex2D(depthBuffer,uvscreen).r; + float front = tex2D(frontBuffer,uvscreen).r; + + if (depth <= front) + { + OUT_col = vec4(0,0,0,0); + return; + } + + else if ( obj_test < depth ) + depth = obj_test; + if ( front >= 0.0) + depth -= front; + + float diff = 1.0; + vec3 col = fogColor.rgb; + if (textured != 0.0) + { + vec2 offset = viewpoint + ((-0.5 + (texscale * uvscreen)) * numtiles); + + vec2 mod1 = tex2D(density,(offset + (modspeed.xy*accumTime))).rg; + vec2 mod2= tex2D(density,(offset + (modspeed.zw*accumTime))).rg; + diff = (mod2.r + mod1.r) * modstrength; + col *= (2.0 - ((mod1.g + mod2.g) * fadesize))/2.0; + } + + col *= ambientColor; + + vec4 returnColor = vec4(col, 1.0 - saturate(exp(-fogDensity * depth * diff * fadesize))); + + OUT_col = hdrEncode(returnColor); +} diff --git a/Templates/Empty/game/shaders/common/VolumetricFog/gl/VFogPreP.glsl b/Templates/Empty/game/shaders/common/VolumetricFog/gl/VFogPreP.glsl new file mode 100644 index 000000000..017ea6ef8 --- /dev/null +++ b/Templates/Empty/game/shaders/common/VolumetricFog/gl/VFogPreP.glsl @@ -0,0 +1,37 @@ +//----------------------------------------------------------------------------- +// Copyright (c) 2012 GarageGames, LLC +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. +//----------------------------------------------------------------------------- + +#include "../../gl/hlslCompat.glsl" + +in vec4 _hpos; +#define IN_hpos _hpos + +out vec4 OUT_col; + +void main() +{ + float OUT; + clip( IN_hpos.w ); + OUT = IN_hpos.w; + + OUT_col = vec4(OUT,0,0,1); +} diff --git a/Templates/Empty/game/shaders/common/VolumetricFog/gl/VFogPreV.glsl b/Templates/Empty/game/shaders/common/VolumetricFog/gl/VFogPreV.glsl new file mode 100644 index 000000000..2f2a1318a --- /dev/null +++ b/Templates/Empty/game/shaders/common/VolumetricFog/gl/VFogPreV.glsl @@ -0,0 +1,42 @@ +//----------------------------------------------------------------------------- +// Copyright (c) 2012 GarageGames, LLC +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. +//----------------------------------------------------------------------------- + +#include "../../gl/hlslCompat.glsl" + +in vec4 vPosition; +#define IN_position vPosition + +out vec4 _hpos; +#define OUT_hpos _hpos + +uniform mat4 modelView; + +void main() +{ + vec4 inPos = IN_position; + inPos.w = 1.0; + + OUT_hpos = tMul( modelView, inPos ); + + gl_Position = OUT_hpos; + correctSSP(gl_Position); +} diff --git a/Templates/Empty/game/shaders/common/VolumetricFog/gl/VFogRefl.glsl b/Templates/Empty/game/shaders/common/VolumetricFog/gl/VFogRefl.glsl new file mode 100644 index 000000000..78e149fbf --- /dev/null +++ b/Templates/Empty/game/shaders/common/VolumetricFog/gl/VFogRefl.glsl @@ -0,0 +1,33 @@ +//----------------------------------------------------------------------------- +// Copyright (c) 2012 GarageGames, LLC +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. +//----------------------------------------------------------------------------- + +#include "../../gl/hlslCompat.glsl" + +uniform vec4 fogColor; +uniform float fogDensity; +uniform float reflStrength; +out vec4 OUT_col; + +void main() +{ + OUT_col = vec4(fogColor.rgb,saturate(fogDensity*reflStrength)); +} diff --git a/Templates/Empty/game/shaders/common/VolumetricFog/gl/VFogV.glsl b/Templates/Empty/game/shaders/common/VolumetricFog/gl/VFogV.glsl new file mode 100644 index 000000000..57b3ba87e --- /dev/null +++ b/Templates/Empty/game/shaders/common/VolumetricFog/gl/VFogV.glsl @@ -0,0 +1,38 @@ +//----------------------------------------------------------------------------- +// Copyright (c) 2012 GarageGames, LLC +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. +//----------------------------------------------------------------------------- + +#include "../../gl/hlslCompat.glsl" + +in vec4 vPosition; +#define IN_position vPosition + +out vec4 _hpos; +#define OUT_hpos _hpos + +uniform mat4 modelView; + +void main() +{ + OUT_hpos = tMul(modelView, IN_position); + gl_Position = OUT_hpos; + correctSSP(gl_Position); +} diff --git a/Templates/Empty/game/shaders/common/postFx/VolFogGlowP.hlsl b/Templates/Empty/game/shaders/common/postFx/VolFogGlowP.hlsl new file mode 100644 index 000000000..8a61b5928 --- /dev/null +++ b/Templates/Empty/game/shaders/common/postFx/VolFogGlowP.hlsl @@ -0,0 +1,74 @@ +//----------------------------------------------------------------------------- +// Copyright (c) 2014 R.G.S. - Richards Game Studio, the Netherlands +// http://www.richardsgamestudio.com/ +// +// If you find this code useful or you are feeling particularly generous I +// would ask that you please go to http://www.richardsgamestudio.com/ then +// choose Donations from the menu on the left side and make a donation to +// Richards Game Studio. It will be highly appreciated. +// +// The MIT License: +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. +//----------------------------------------------------------------------------- + +// Volumetric Fog Glow postFx pixel shader V1.00 + +#include "./postFx.hlsl" + +uniform sampler2D diffuseMap : register(S0); +uniform float strength; + +struct VertToPix +{ + float4 hpos : POSITION; + + float2 uv0 : TEXCOORD0; + float2 uv1 : TEXCOORD1; + float2 uv2 : TEXCOORD2; + float2 uv3 : TEXCOORD3; + + float2 uv4 : TEXCOORD4; + float2 uv5 : TEXCOORD5; + float2 uv6 : TEXCOORD6; + float2 uv7 : TEXCOORD7; +}; + +float4 main( VertToPix IN ) : COLOR +{ + float4 kernel = float4( 0.175, 0.275, 0.375, 0.475 ) * strength; + + float4 OUT = 0; + OUT += tex2D( diffuseMap, IN.uv0 ) * kernel.x; + OUT += tex2D( diffuseMap, IN.uv1 ) * kernel.y; + OUT += tex2D( diffuseMap, IN.uv2 ) * kernel.z; + OUT += tex2D( diffuseMap, IN.uv3 ) * kernel.w; + + OUT += tex2D( diffuseMap, IN.uv4 ) * kernel.x; + OUT += tex2D( diffuseMap, IN.uv5 ) * kernel.y; + OUT += tex2D( diffuseMap, IN.uv6 ) * kernel.z; + OUT += tex2D( diffuseMap, IN.uv7 ) * kernel.w; + + // Calculate a lumenance value in the alpha so we + // can use alpha test to save fillrate. + float3 rgb2lum = float3( 0.30, 0.59, 0.11 ); + OUT.a = dot( OUT.rgb, rgb2lum ); + + return OUT; +} diff --git a/Templates/Empty/game/shaders/common/postFx/gl/VolFogGlowP.glsl b/Templates/Empty/game/shaders/common/postFx/gl/VolFogGlowP.glsl new file mode 100644 index 000000000..01b072dd9 --- /dev/null +++ b/Templates/Empty/game/shaders/common/postFx/gl/VolFogGlowP.glsl @@ -0,0 +1,67 @@ +//----------------------------------------------------------------------------- +// Copyright (c) 2014 R.G.S. - Richards Game Studio, the Netherlands +// http://www.richardsgamestudio.com/ +// +// If you find this code useful or you are feeling particularly generous I +// would ask that you please go to http://www.richardsgamestudio.com/ then +// choose Donations from the menu on the left side and make a donation to +// Richards Game Studio. It will be highly appreciated. +// +// The MIT License: +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. +//----------------------------------------------------------------------------- + +// Volumetric Fog Glow postFx pixel shader V1.00 + +uniform sampler2D diffuseMap; +uniform float strength; + +out vec4 OUT_col; + +in vec2 uv0; +in vec2 uv1; +in vec2 uv2; +in vec2 uv3; + +in vec2 uv4; +in vec2 uv5; +in vec2 uv6; +in vec2 uv7; + +void main() +{ + vec4 kernel = vec4( 0.175, 0.275, 0.375, 0.475 ) * strength; + + OUT_col = vec4(0); + OUT_col += texture( diffuseMap, uv0 ) * kernel.x; + OUT_col += texture( diffuseMap, uv1 ) * kernel.y; + OUT_col += texture( diffuseMap, uv2 ) * kernel.z; + OUT_col += texture( diffuseMap, uv3 ) * kernel.w; + + OUT_col += texture( diffuseMap, uv4 ) * kernel.x; + OUT_col += texture( diffuseMap, uv5 ) * kernel.y; + OUT_col += texture( diffuseMap, uv6 ) * kernel.z; + OUT_col += texture( diffuseMap, uv7 ) * kernel.w; + + // Calculate a lumenance value in the alpha so we + // can use alpha test to save fillrate. + vec3 rgb2lum = vec3( 0.30, 0.59, 0.11 ); + OUT_col.a = dot( OUT_col.rgb, rgb2lum ); +} diff --git a/Templates/Empty/game/tools/classIcons/VolumetricFog.png b/Templates/Empty/game/tools/classIcons/VolumetricFog.png new file mode 100644 index 0000000000000000000000000000000000000000..5dc516cb53d99e0ae0d125507d6d95c9a2942107 GIT binary patch literal 3642 zcmV-A4#n|_P)KLZ*U+IBfRsybQWXdwQbLP>6pAqfylh#{fb6;Z(vMMVS~$e@S=j*ftg6;Uhf59&ghTmgWD0l;*T zI709Y^p6lP1rIRMx#05C~cW=H_Aw*bJ-5DT&Z2n+x)QHX^p z00esgV8|mQcmRZ%02D^@S3L16t`O%c004NIvOKvYIYoh62rY33S640`D9%Y2D-rV&neh&#Q1i z007~1e$oCcFS8neI|hJl{-P!B1ZZ9hpmq0)X0i`JwE&>$+E?>%_LC6RbVIkUx0b+_+BaR3cnT7Zv!AJxW zizFb)h!jyGOOZ85F;a?DAXP{m@;!0_IfqH8(HlgRxt7s3}k3K`kFu>>-2Q$QMFfPW!La{h336o>X zu_CMttHv6zR;&ZNiS=X8v3CR#fknUxHUxJ0uoBa_M6WNWeqIg~6QE69c9o#eyhGvpiOA@W-aonk<7r1(?fC{oI5N*U!4 zfg=2N-7=cNnjjOr{yriy6mMFgG#l znCF=fnQv8CDz++o6_Lscl}eQ+l^ZHARH>?_s@|##Rr6KLRFA1%Q+=*RRWnoLsR`7U zt5vFIcfW3@?wFpwUVxrVZ>QdQz32KIeJ}k~{cZZE^+ya? z2D1z#2HOnI7(B%_ac?{wFUQ;QQA1tBKtrWrm0_3Rgps+?Jfqb{jYbcQX~taRB;#$y zZN{S}1|}gUOHJxc?wV3fxuz+mJ4`!F$IZ;mqRrNsHJd##*D~ju=bP7?-?v~|cv>vB zsJ6IeNwVZxrdjT`yl#bBIa#GxRa#xMMy;K#CDyyGyQdMSxlWT#tDe?p!?5wT$+oGt z8L;Kp2HUQ-ZMJ=3XJQv;x5ci*?vuTfeY$;({XGW_huIFR9a(?@3)XSs8O^N5RyOM=TTmp(3=8^+zpz2r)C z^>JO{deZfso3oq3?Wo(Y?l$ge?uXo;%ru`Vo>?<<(8I_>;8Eq#KMS9gFl*neeosSB zfoHYnBQIkwkyowPu(zdms`p{<7e4kra-ZWq<2*OsGTvEV%s0Td$hXT+!*8Bnh2KMe zBmZRodjHV?r+_5^X9J0WL4jKW`}lf%A-|44I@@LTvf1rHjG(ze6+w@Jt%Bvjts!X0 z?2xS?_ve_-kiKB_KiJlZ$9G`c^=E@oNG)mWWaNo-3TIW8)$Hg0Ub-~8?KhvJ>$ z3*&nim@mj(aCxE5!t{lw7O5^0EIO7zOo&c6l<+|iDySBWCGrz@C5{St!X3hAA}`T4 z(TLbXTq+(;@<=L8dXnssyft|w#WSTW<++3>sgS%(4NTpeI-VAqb|7ssJvzNHgOZVu zaYCvgO_R1~>SyL=cFU|~g|hy|Zi}}s9+d~lYqOB71z9Z$wnC=pR9Yz4DhIM>Wmjgu z&56o6maCpC&F##y%G;1PobR9i?GnNg;gYtchD%p19a!eQtZF&3JaKv33gZ<8D~47E ztUS1iwkmDaPpj=$m#%)jCVEY4fnLGNg2A-`YwHVD3gv};>)hAvT~AmqS>Lr``i7kw zJ{5_It`yrBmlc25DBO7E8;5VoznR>Ww5hAaxn$2~(q`%A-YuS64wkBy=9dm`4cXeX z4c}I@?e+FW+b@^RDBHV(wnMq2zdX3SWv9u`%{xC-q*U}&`cyXV(%rRT*Z6MH?i+i& z_B8C(+grT%{XWUQ+f@NoP1R=AW&26{v-dx)iK^-Nmiuj8txj!m?Z*Ss1N{dh4z}01 z)YTo*JycSU)+_5r4#yw9{+;i4Ee$peRgIj+;v;ZGdF1K$3E%e~4LaI(jC-u%2h$&R z9cLXcYC@Xwnns&bn)_Q~Te?roKGD|d-g^8;+aC{{G(1^(O7m37Y1-+6)01cN&y1aw zoqc{T`P^XJqPBbIW6s}d4{z_f5Om?vMgNQEJG?v2T=KYd^0M3I6IZxbny)%vZR&LD zJpPl@Psh8QyPB@KTx+@RdcC!KX7}kEo;S|j^u2lU7XQ}Oo;f|;z4Ll+_r>@1-xl3| zawq-H%e&ckC+@AhPrP6BKT#_XdT7&;F71j}Joy zkC~6lh7E@6o;W@^IpRNZ{ptLtL(gQ-CY~4mqW;US7Zxvm_|@yz&e53Bp_lTPlfP|z zrTyx_>lv@x#=^!PzR7qqF<$gm`|ZJZ+;<)Cqu&ot2z=0000WV@Og>004R=004l4008;_004mL004C`008P>0026e000+nl3&F} z000AJNkl9kNXb9;;<2!NH znsiy4Zgt--Ts3J^cd%(1u`#6(l!CQskx~@vbJU9r;0%Z}-nldP{@pZ<@$A0jJIfD{)_^I^O+{5a?kaZ= zQ1QH@C8J3IT;+bCP#^WI>8aTM(st)^=CTvfBP(4kU3SNw4!Nyzn?IM%#jJvrP^d~n z8c!8!MN3ALDAYkAMDNkfM;q6lY+U7`^zna2<4gX>yRzZ=&WF{h&WojAWU=Mh`F9iV z`2tq?ru+7NO%vKR9oT1kcJGl5M_!re_^skh-z9&e(M)ZqhVX_UuWxB1U2k!F;;;5Q z*&FRw(qnwM>)}wEx)g{gkUH*6b2f?j+0kWfF;(soz|O zUC1#zcb9>)gNGV|tzXaQ7MrE5=Xcs|Pq*F4P5bRa9@Fq)2K>}E*I?&u(oUM9Tfiw6 z@H~&2wT(2z>oCLe@UkznOLt_UX5G4a>e~+DxHbxf^h?SUA&S!_@@^hy!68r^!ZS-~ z(x7+mex{eNM#cxnj_Lw|%0Wo{amOFSH~`19T*Rq+Tp*WvBm zZxL&0LJW%*T_|zu?)Zb|ja!-sR#$NEl11rant`dG$mMM8l;T3wFlJQZ-7L_ws)_7K zmboYQ(S?rI5qcvQEk!F;QB<0bd|bcqH$UGRLOwHiGulaKcRMW)Hsa5g^Z3ydrmY#S zTi2MJnZZ{K%;vUaZPkO;@su>V4IintAQpulf#4zl^$1-NnpE=Iq3Y zi9P4$&t*KkD?aHv#cRIuZDMoua?!9O~L4he>M0}s!;H8_931#%b~$8B-rIfJ0(L8H|{ z1WF+{Bn<*dZz8n^krZHCQUgqt*af&n+@RfTZXj{bu4hb-PP}nPL>VW2CWMLdW!3$y zQZl-4@&vS2SCK4M?Y+KleQT{|H!|)U-F;-Uq2!5{@?lc z&maBE|Mma*!T{S^vco*(`GEHN3;m?C0sD&5mV%DhD^hJ2FoKDC3I=W~EwZPu!>oF(nZtcxD zNsc@=P1{X1^|$lo_{ZVYzie?m(bwU0Jef?)*U{IlR(oc@Hd9p#n(k}+^?UX9Oucqr zr{nP?daYi!TdkJ)CVHKwX=1+7uVa4fH~O0tI`ICOBR9wjWd&9D{FWqgnSghQw z8m~M**v@j6a=kaNX0vrTQRC#RG*y#uEW^w8&f@dQv8GMay^-d4Im8vrHh;CbgsT~ zaSU&S@BscY4Zl*)<3_U`eHDK7{$$<}{I@2{x%s1p2b%DK`>M#Tg%6%*d6t>4Y*OS| zY@VB>JIoiF>o0^Cu;pa(PcAWu_?>_5lJ%@vW1N!t@9S7~v07zWg?r)9i|`58RjP|f zY$5m`kJGlf))&w~i_yOZt}Ve2=Q&docLV$z;!n%8QNvFr4JJ04hSSy*{7<9l3@i9= zkCz?$wVlSrw;lD`PT`Gq_>Fp9Z#J9yoA4XxA&vfQzFC%UWBX_Q4TqmPw(Ibn|HgjP zNZyw2wRvs9wcs9l9h%qCYc_k)YyY+Em*cMb{oGFVbit3FMiczdX#BVjKMMYf!Jxwb z3f{2beuSe4?z5~YqidMYSR282#%6`V%5~~%^CSlU=0ZPFK{l`#Y|pqJEWDfwAH=hD z2mD`H99(#y43rZ8j`Lm*|Gr`TYyLJI*W$EPoJV(Il)rViUO#*&_)Y$O*)d|vZe@dJ zyQRT7!!Xg{pJ9#lpF**D#Qu$5uV-FrcpzxY=lb}!9PeKJop-P|CdNPDoz5^H@qIV7 zpaCB+PwVwkY`tEqi8aP7#Kz^DId*IymXPy194%Ji)ZM~cJ^#|&0{22}{Hp9aY@QFh z_Ev-cUQgeGKovg|yw~u2!9QVdXl^5LPW-DZJawaQ;&i&ik8W!9I{xIZ;pLGB&#Dp6 zteo$3~A`0bC>jY}4*ybyl} z=HMVq{9@txUE$t=LwI0iVtv`|79+uT|KUR&#o^_!$e)Srlw}rzXLvOi`|GkyLf+pi zvRHVa%wsE`dobAO3U7!yknQDsJ~zBSg}^oV&iJu>A2g%vr8|q8jfQ3anjagx%d*Hf z95%ooga?S1bFm6uP|tlVeic4f&(Sq4c;I@QdH65o78JG2R-1g5y__ruS#%n%l|DRam+G3%u!zaYMqQX6X9>D{Q zZ)`52i^*i#vV#94NgQzv`fGT27#R?VUp2v}_)jfacOvj#mgzVo?xKJ42)hDzdTJ&w z3g#ahz-x*)&Cf*e|KsuW^XSjvH(Wnff0K)!gZuu-D*b2sO|j0X40 zKm0HJer;ES4F&%7FYwQ=-E@#IyoP6x9~1CD1Pt36*zzGioO?VOfFC|W)3mT0e zP*45!9J*Cw^*h0RI95+p952;GJrzA5elEN&R>j#`EdH<-Jhvv3D*jK`^G<{gVAxtA zJW!T-re8)c@7){oKK-&inY=IWUo5P#Fa0fkYQ-+`V~5_rJq_1;%lX3B{2(J={$_NXOrdnP{5c2ws`o~$vfO}3?i7jU_e93+jGcNE19&D@c%X{`V zy7l?6qi@68Q9kHKx9ZkpMNjelWo%V=>l3D}aL;|RoQ}s196eJqQ06S@}r^ zi=G01PlX3c*}i}W(6j1k(42un;$7M6_#4j1Z*SOJchee=OTl$An=TD_$Bn(J@IPG& z4{#X+*)MsLeQ91I;OAl)FY*I-JH)RYpT~Vi!UMsxSO3Ga-5O6P{-v6rAC1`Vu!w~( z=JVxLzOS>Kl8>0%3}UG8UUUU_H_F<)vJjV8o0Y$e@FU{>3vhiMm%skRkS~#+j?J~c z_WrwH3oj5Br`79+iQxY_5PpXTM$b}oJjZRsSjF-4JKIQ?hT|hU_+E7r7w{DLR-?GY zKDROY6l<7ov2ottP`B;C zi-_%@p3cHAb^KL={>CzV!V7UhK#JfCyI5qJIG=!9!GCMaaV7qTqauPgpr2CqTLcHk zJwvxm4ROCHik`jnXYt={Nxmf8uV(N7mh18G0s6A==mXqDc;K=Lz{14G?G|}5_)p4C zM~h9#58<_1kW39cK$_UG&-L?Wo4kta=i9A?2MN1<#w&q7W?{wC{l1JhawZ9x7iglV zufyxvvlxFT?q#tt688!GqH~$10DBQrVsXU(6x{o3cinpLz4yTVHF@w_c!2kjSNiLO zdV?B=PnEHGG!Do2?%jJYn&@%a?RG7eys2~(Ge#b+-Na66wR#J@PU;euz<-1MApEoc zZ}s+c8U0y(6PIhY)i<@nG!^@sEE|YkP%-wkpYlB!aerz@cX3}4u!K)YM>|gPf4@!s zA5BSD2NM4!9)!d>rm^t1_Sz#9E!RcdT8Slp?U`#WxX<;I@Cj+-B771auLiwtc&v_# z%qYP>a0CDPIA2*~j-%s7lC~ZAXFN)G0S^%8?|7g&b{GB$^Dqd1=NZ>@?JvU5n@Rji z_#jD>6nky-M_q7#jXR^O+|9 zMAw6Ja2H|8lLirZ%+Ukkjk>OpA7U>Ijuc(1iq9g+pMvoek2dC%&m#{Pu5F@c!J4@}7Hc zo+CdhyT}c`lDRKSTtn3Q@(c{xl@Xb$1y`T{jFWDB|A~r)e+B1Ddy4-SS_Us*g{FysL z7uEflI#mcU2kwWQkcS^+zAQE!PuC6}2!c5G!UyEL@YN+@bIEyM%IBg)!3y{T*F$)z z_<-?TYxscIr<(CW&MyT2{lUgY$oPs55!|EyTL#{pqU-gIrOuTP@%ua71=T?p7=iKahxgiUgiD9uLSoK^kG+%_Y1B=a1Y)G{+288S?-DZc`+CeuP!QH7kxh( zEh^r7yB3{)?Ek}ieH$wu z6L*YkYpc%%|H*e^iI=7Noxkv_;#Z!!VS6@R`WvYqCNsgmAEg~feudi^)WD5gmnL7C zt{K;t=B5GB3J)CeJ^E#IOS!gY{)9d!kQnf*!UpkNQzys@iynxV@B_BWx50TvytMEN zdVeH*Fk7#G?Z~gQj5-#&dqsUz{DrSwfp_AB;M`xCQ5-A5FMJA`uk)2R3fxoX8~Lm7 zf_yV(KRl4EBWyrixWm2gh1dRE_!@gPDtaAt?XE0(N^F+*y6(zc!QX{gla(Dyy_)i7 z4~w_X*04j;p5ZrG{GqZY!+)RiI(`um=hsP66I>{p{Sb*AWu;j3aVG*dTnBgxuYgw> zf~Nri&&BGh0l-{rL>FuuBK=^%brbhTtL=x_%RTrfBF>{K!FhODd+%OLEPMt2^%>(- zXl(h0x;;7{9oG~5w<$2%Gk?O%g*tU7%zvWJ{R#7&V$U}6xar^n@G-NO{v?@+wP!I6 z0LyVy5TqVnYNzNm_zInbE(ov4hZW}^TYw*OUCe7;qZFW{wv1P+1LK_~ zDPYTdQkQR5xaWKvc|3Ii>QtUO7X?#rJ?Qr{dtoj%?r(ds@4w$_I`RM}fc(}!q2O6q z^4j&9_=-JzyR2-j0YCG3$`)yeu7#hb!UMSZ+sZZ?Khf|2bI%zbSp_-%=u)|#`W=y|5QBRWChPY%3O7GXbi#92A?;GVbhc|A3Ye8W_Dryhu+ zMi!Le#8WSZFD6dve1(Via_TmgcyB2n8t@Mu(E0jFpC7@o<%5Fj!LZxa#CPvks|Y?o zfR*S1@&NSjoh?G%N4+Sf9>D%`E}u)$ANE)ulODGHu|2NW$#Xt;$KrqR0r)@stA|&a z-?s3;Sn|Sa^*iz-s)X*EVOm>kOhB-lZ!CEjmb9v+se6^>axRwq>BoZqS_AzpahnSi z{6p8|4f=L8YBY7F2RhUf?XBcT2L2FTYx#Vw#=L@eGeYOGP5$_;K^LN{vk-oS=4T4M zj$MYt>vGuJ0N>ZRTT3kbu~OjL^5d`kIrEmQYj z(p}kec<`;*1^BgB`aFYI%vE$Aqc8MTcpk>_Ta!fR4eIG8(dUW*w$!u4U)a8qc#Ge| zOQRf|Uy0vD=zM6U46!5rBlv(%h@asV{hp18en4kEZbw(@s$>{E_;0tTQ;+_~{TGHf z58o~{xE~BQ1|fX_+=RHU&Y%r!cq#RoNbqU%(ws$S>skC81KuapCGD9xn}2w(F1R=3 zOJWDTUKjiyg8NvV`jb8KZQ%iiwIkcxEBxZbo=~5#=FFWi-fII7Aph3z(wW422mYb^ zrH2m!0;ob4G}_~-TvvZE>@i+fP5jLY4~$s^_s8PbZF*I)EEdiXu_nNBVE zh5j=K{&9;Jp6x#4=D_i=%)HnR1tfQ2FY3VuBk@@Zf!7@dM$D(PAO-UU2?*+z& z*(LbD9Sny<`8@Tu3jdS~W`^Sswt8B05%JjIC!OxfXmoo{n2*3aWfZT7-;@(=Wk={3 zwylj55B~EJ{fsMjUKV}&ZvI@3@2YrSRPh~ozqD889KTzz|D4nB*>io4Ea;o_=$v-T zFgg#<4_^aoZeOFdwK4v4@#w7(slg=gJvITK4C z5?0V>O8iseG&)A`P8*@Z{jxzl54}HMrfrYjZ`Ij8aVLs`0Kbpu&y75mIw1Q!d|>7E zL-p>cA@B3n_uYv(q3u$bGkYTaL08oc(LY$hlNH|i03ATS&}4xC=YNL(EF;DvXtyRB zK9x3*#|HK0IAVV4&GwjiBrj_uusi*}Ja<@N3J-YrxhOn%C$42;(KAEvd@))T-(#PQ z(0vxX_dZz@zw9w#UgFp>;ie=zka(EO`}i8F+U|n7+f;#na5R){{GGa&m;CUDXzZy2 zua}^Gtm5y6m8FgWFN~q@Pbv$+iw(fJ@BwYr4wm>|#rcWUK`itCc49z_3ik#_rhc z3ePcq2g1KHesMw_m3-j6A2(}^&gYbi+Wx{_Y;0`&%rXx(Cf0HtjMv=4VTh-LkHdo<1@+stGLW|Z#d|q4{oSS6~Y6^fkKW) zmyr6qk$Qa97s(k10}l+c48IAlSzfUS@h8EF2j4X&2)qj);BVM><5K)?A)muic551N zW=4O=u~+D}nFW8M`wh6K9^lP+cuKuFkFX;O(-FAOa{6jm+MdhkG(4>ZvkvG#rK~iN z{ge+nA=a?|BCPZ@Jaq2Jzq5h5a%Z0W--FM_v$8AOjdU`P;0MC$Ts}|!fT{@3SF3)H z&!2hvzk;U~pVNGf^5{~7cdpWeXZj@0i2rBin1 z#2(y#*rKn_ad*CBeZ{#up?)~xtPbff@Hu}1-Ns@&G!g~(LAvaC;y<_>SnyAscO-Tq z{Cs8(@vosd(}&FOjO`QJxJxhJD|IyT2k5REmnMKV@F?qP#_rsl*NL<0!kyD^`7ols zN4f1oLwy^!OD+2T9t#WHh538`ntI<|yw5}*%YJxXUT1%gd=`ri0q?J90E(6QH~2A) z{6z3i-DK{s-IFHz#K9}y|B5<4{>*W-Wt)CP1Ae&GZIsvi`gVzAMJziDBZf$0p8)5%He+9|-Q{^X5SB<~ze~gl#luv*;iKzi}BIN4v;ntUee$ zqy7rcJ1Oge#P+?B_w)Io2Jg5*EXU#fSi4Jlti*~wf(H&{o{0a)v+;QBpQ!(rWh?=s z^m(;D1m0D~D8}=mc!pkvb$2-YAq4mI6)K&qR`-7#6haX#^|cb2+3h0WW;?(drM5 z>l*lb0=;OeY13o z1GF)OACKAq-=LGm?R0F<%_%UNs0({aIc^gN^4Q!E=hY#)p6jL_+KbTbF~Ti84iBh%px^Z_ zx#Y{Q*jB6)-9|Y0bMXHMgvhxif6oj2 z?%WdCE%9CI3=w#r&)2^eOF4ZB51i8IDm3vIy6r{ap7c%P@Co^UPl0c9m@dK5$*d%g z1D`SVF|iyj_u!8FXXa1rp+Pr*??cA9a3}hZ&kxN>bjaa{nr-_69yrv8P5ca09g=sh z!P%jzCG*O@dv~0e<67_*Fb|bwUd$QaTlU}&OPppMQe{QY3tr=fobQxIxH=C{8?zscUZd zLwDHO*fyN^9&~N(p4sCxl-YBA9l5^5%kW6;2lduJnbYWqcEQq~>Z7Lc zz^OaxQy>VB{SoU)CQ{FaccunBx2M!muu3(Wg7@`$Jat&;x)jTBNCjQ|K`Qu{`4QX$f6-HnQ^g0@hWtcupOn9Z2Us_Qqk((Y%{78b_kcUG8yolk zhu}Dk8?4WAx72x?(&rDPp7PS)W(D=IDS80iCV3Of!4rQ=zSU@_!UGbYEx1Q7Ei~iq z7v#gtleMFaQNF0wTbzea8Zmht`jR^OLhuh?MkY!2wp{od@T_XJSdGoCkN70)fLSl?}zZ#js<+Kf^LGhtZ+*-SuI( zHzMB?%XFmXQ14NWnZpKqlJ8jX6rfMBhbpK~+@U?J)$jcS-*Z^$BfdW)kn`<_jkHr) z8>dg@^*9LXiu3LKKs^LqBJ1NoJ#`6V1pn!j^VGz39(y#1 zLdH3cNo>GBG_f-1|0N4Wt?2u~pl8tcqZl3q_l5$YSl}>FSY&Y$3hqf9)F}vr;Kgz} zOR@0B(1SPCNS2;>jf^X76}KCy;GK9BSGZ^X!2OgL*v$t;Z^(t|qwV#Esy5 zXXg{c_KfwEVyR0Fqrc%_gZp22YlybTJ%poJ>P?yBIOOr@1b2+?Y#7$bavizg5?&sJ z7yjG}{_q>7xhHXhI6Jtr6&!ow3HVMfFEY)D$xaudrR^IcvtEJQU^#X+Xw>9 zI4{?;G{SPbMAa{3_@@SaG@nfAv)YUI|KT3^ryuYEagS~26a6Fg*(4S#d4*W?Y9{!v z*Wam89eE1Rmi{Kh=HyG^Q{mOhqJPzdZSWW3@d@r~U4ima2Fb5|r zcB9C?6aL_SBn=N-lLu$$u!|yFj0`-$G7sti>S8z?3>QiVJFoIj@_d#JCyQdx_23%!9ysE%;sh%k?xBOgV-TZr$#?T1 zb@mg}=VkP{;ni{}aUEC@?<4du^SKxGOX>s~i@#1ac<1dsvG5N2HLnv_Wd97E2dW%|xx{&v&DSjWH>(Zm4TiiY?{i%ztkZeLI2_W? zt;WHlQ#rp!@Lz7g|27G%eqa5ONlzW3AzCM?-G{COQVB(t#-?hmo}(F82pm){k49APHo6~mZ-{0E6e#*mQ@E> z$p^rDbWA+N3f}K5%Oq3b0s0)7MOTQQYSz)QzGf9Z@u!}JCHCB(F1ex5QJ(f zcEjl0p3+B+4RHe+E-Opn#axBw0S+xz@NMxg@Hw~gK6Hb;&k*;q)M37#yHkA{v}Y54 zWRJ-6)(YK&oLKrD^%!>nwN`Q3Jq zE+e)Dm)QHHo$!GAIXa|_IIhfl1i#c<(+T6dOBz5P5FSXLFSwtihpZclj#*#Z))x`$ zhGd;(bir^_FVz>MKji-~!d^Tx)JZy>`O;x=_m0@OlzNH3AUzp+)(gnEhtvgTX}cNG zUzIvx^fY|ReVdi-OP)i&b@pxVlMuYi{dbBL2FPI6Ke-zfZ*(iG>K_X3vy63DQ)j2YX2HXd8&4had{y+i7CcKm1ApIB@4VN-61NvC3tmNciOnScWWV^QVcU=p zfcHm~0rIFHJ!*&__M<+x7qoR>KVm*pwjbfHv0;Ce=b@JsALRItn6{GW$|#}pIqsOk z3HXNxf;w@3kMicyABMya`i11z$m>aT6donir(_%dki7dSi$73esroBL{>}XKLM&yZ zS%}UdJMw~i=zeU^G z>v7%>%mdtP;vd)ttPh@sTmC`4*&OSwaH~lk?sm);ZGbT?TOF4ePzKZY1^&_xA1ihm~~_^bM)U*6@1^FL8Td zUe#CrR=?>FhCP23QlFTP75LA3`=1@~eb@R3-lw>xkq>jOOBpQcGu?Sp^#S)r$a z-6-5wI}H}Df%6^sJOSPNqRTXv_ct`WP{w;o_(5nuc!2uNG?e^?>-W?vSXSA$Tz8cp zo#>Mf;6=;zGweg`UPc@g{4WqJa=qKD?5~FNM#nF}|AW!UD6!Pt7ZU%`|1$><)JgmW z|Fn~);D3`ev}L!<)_Xs0z3+D{d;tEyKd%%2)h^{HbSZHOhY>4zOaw2GKL{UCuR=FT zeW0XI4-bg{MAQvOyYye+0fwa!iXW4B3lG$}Z&`Ri@=n<%UJexXH`WjFJb~y*nI?hY zpXGH!!T)M|up#*09t=u-8l7(6zrQW`=d!DKfu8Gy_zSwKs{aE2Z$&T0)Pq62^n0YA za)OQn|Nf*-ohBqdBQBJT|32l1jzI@C+vGtzZfmtzjO>=*B!6A`P4}s1ArQYm=KFi# z{bS0@pZkx&Ip6Dl3a)9eSJv9x|J1?*P`%s`xZW z(zWn_)X_rG0YAFe0{^?fGi9`nNe|wSF4U8>O?=|M!jw9z2KUnU5<6bLd1ERbK>xrG z!pD`x;Y1fu2Uvqo^Z@-Q(E}8E!T$yFZ5eT2(RUP@L-3Ex<-Vinclc!zq6;iKQ}O|s zcfo(LasQ(V|4wgPa8Ep{))jTS-~Sc7Uz@lXg7<61z>6h)9ER|~ENSw*i0EQo&XSON z4ZGn1*835^-MInhtARm}Q7`;Ra4+?f5S|_}p)!9Qu9`REMODXJaYGh<4-cRd!9V+n zf2J>1{C|x<2>u(5(Mq<#`%rLiY~HDG%<~Ckd)V*)An)5o-f8giZi6_6#jQKCjURn4 zmi8d^GqxFc`FyqBmTlZVaDCgKPwv&-p6`R#q2CL42t&ExUi>%0rYU-BKic8`v?g(8 ze@-7DI8<<~RK#wFz+=wWT-vtT!-Q z(H8ywiQ6$d^oggAI>3MhTH%4;$4@8V`CvkQIRxh{LkPhCLC{`y;D-a5f5rZ8neEBz z^OQOWabNmQVjC2;M9){c2#bCwWm}Pud+NeGKcS{BysYm`v8U7nH~4*|5Crbj998qp zw&2)+|AO<<#P2@sYofo@Q@lGN&te^{W8Cm_EaO&`(vKl8jPV~=<%{s@p472C^-}5M zdgg6_U{X7J3rAYnt!R_DJT>4R+1dA>x=*RUzazLMo@_`Qhu+Ex@0_sMYUaJL^|E#NJ5Sw|^;y^9?Z!Y6ex9NBpYq(7Sn&VwKUN!p|7{+mf(oD> z5X4gF!%au<(lz&0f23LO6T^pW!3Sj*ykFS`b(?E@HOh*f0r#CA{X}qY7I{x_Z$|ls z2KUH}N~f_bPx$)`8rbmEy#yx~-hRV61qYA!`-6c+|8@&94)ib2dD%ALpR{pdz&qjl zMI=11UeAc*ee%vl1Om8^DK^CQ03d<;N9ZN+46frt-tDN97VJ?+f5QtQR`^qJk6xy(7VUTDlZN1)xRz+}4xP>I zk=g$)%bpo5ZlDDJ3+b1j3l>=x`@?WQXf&y3c>I-m7Ih8s;=ZNc@o=@hVW?Mdxr+$A zBmYXVoPI~HoAA^TJv1B+yYRpxnIEw|)>X@OeEwlQaF6UGsq4GG+QRK~sRJyRlX0{Y zZsF&Ffv?yq8@u!Zy=e>y~uP0rd`E$!UaPWv#CM#tBx}@MtA95B4fvGIK z4G&}tnAkMlt7-UvVH%5$>X9c%-G%$Ksy+xhoPLepgRiHkslAx>ACB>1JiB7Un00mZ zN19V~`Hp`S#zjYgd-UZZ+BBb%etGw)`Bdstg40_3RVg@*Mel-d#&soe9)78KAo=%S zcKpX|qxY~M%l)6~=k8;!ufjdgNlL)~ry4yc$Kx>tO5#6tyqVyiI6YIl;b+LMnL=;1 zS`&Cy9W>EjA$&@?iFF8?x#t-bvdB_R^op zeG!&*b@SzNW?5GUtYp31S<^83%BmGk=1Ud0~~<9tU*n zr*1Cq>rc3$Rq(#1U@ex@Ce9P@+l@e@|It0fd%ZuOk~f3?KzQv0DvW*e?cF}o_1L9$h%HgG*raK2nj1X0K#{ySD>ui z7yPq+c@e^&^szV8zTYc39mZi#@N5~c5%nh-FYsS#u3zfY!uRl8+dc9RSnoFoH>rp4 zBlrjY75?ZG0ih5ujov(KN*1N=O(E-)-E6`cEQ&Uq9l=+CLm+&|T@~nuu zN`pF!^wq0+E9)4jXS1K%gxGJ68_fINHhvHOr|Y*bEOz!*@&)$)ob?ppwS_;U8+9YN4#7Qb^{%?H+>hHII_@JQjq62;y;_sT zY3?tPb=DE<*@Tb9R{fXD@O)mr;OQ0Nx%jQ%ev&4K4!kqJL1hi=5?IM9dP(@8Hz+C| zusDK)4}zff4)#PI%VAmXD(hvf=z)#F%8(a-OJZs0?_p5QTBbV|KUkM(Q}D#|EgFIk z;+5cmxGlH`AvUgfAdBOX>wEZsbt!w6xL@%?ADybgeb;Atn}T;9euhf8ez22)>J$ z^{RsNn03zV$4}7dyzaMIXK0RwrSJhqg!r*i=S<{%xb0ewJ8q$u(1W00^fw#G{z0Gn zPvC))I-JF#Ylgx@z%H@y=VFoN;D5hQo-X4CEm*%7?r^^m^HySK7AQ*^<{ESsKq^gVL3!aM7H>VkWz&x#$5;!JQ)`#%=EbDC?v6^i{q zwwvg#@UeP4pU)c6u6o>P20_TWfI`+8VyRcGHF+3^sp=~xE6ER{V{VJ+h%H!88u2>A zXf^8vNE<3XAU|wH;CwhNC2x@YVTk2^+miJU>__*?wvzq`I$rodgLiB<5KnZ37eMs82Am?KJ1WKTk>T-2ma08y?gDSfP2aR<_^E0U7k7eca|9p)DCzNoa_*k z=1E<)3+i)%cVvGYRk%qLvA~G!a34GbV&{{(GU$CCmsNM*8a%-RZlC&O&+XfN=Uim> z$*Z5eDzN)3bL*OYy-&Yl5%GS~@)W9)qeK}1kjP^x@=`>|5UFF&k6qJe)W*L z7%+*$b9G8OI*rcxU#&R_sn0SW@IZKuUz8zrba*1sr(wkdpj|BMrS++lS*!g71W`)t}yLQTN=mpW?Tr)B~hXDSQw#@}5P%sd$`7QUku|F@~R6KBqqoWUw$tze_1@5^|Phsa2qN59s z9-~ik;iXU7`ClygJJUw~?yvB>o=eo-bKHpUlx5`ku=3ovZO#31$fw%6Y&2r)*qq9L(Q1y!i{}etf_3;J+%GOB-2NfP5j`>weX&Z4e+F@9K2}*aE$A-qg*4%~@f@AoTIBj;+=+#VckE{qR zcpf(!%7Smc{sVBo!v&O%_gU&syuL$uur%^{96?vF&)AOk@oy`BOgUV5g!32-BJ%Qn ze|w>}+=DD9k4LBSG4fRRV9I$*{!SV<=lsDxKu7m3c|fq>y@I6UehkFfjbEA+lPnu#tTE|Z@$LvYId7#&pGDLhseoi-GqL_eMSrIBs&fHahT z8vO2fA98{FnA9om9{98Hl(fu<<^G+K;yw!6!+B+cW-9wT5VBZw;y)^Qq~wOt=p25a zAR3V;lK(9wU!-oHTJnQ;{x|x&=kAEQ2Xz5+1Rsvj7e}l!8_2kQ^wCH6W!^~J;AQ5q zCw%Vv+7p)s@0*J|H^X zzypkeGFufN1orvLO6ddP2reEMmgKEkw39ejs)U%*3K68G^h z`iceP6=G*88Gx)K%lY7rtXLd@|9YK14tc^u1}dbV@PhlW)rC8SP6i%b1rJmfzm$CJ^zZlO z#pnko>!=_I5AH>0p!2UX*5NQeJTI5^P*;jN-zs-_h&)G6;yUrDDL5yJC`(4qI^UwQ zv!tPUel_|y(LDEhI!M>}T%Mur3~6h6hR<+0i$f&Kk1*{*d-wWj6->+<|}e`=vj0)CpjI^6P24!u>w5 zsS!W+Cm*(If^*{H$Y6;Ru?3GzS1j>*1Ah`bc|Q=lgF94sfB)XSBYOm{8Gj?Vj)Oq# zfcB5rvi!cnJ?-!={ICOVmtt|(T#iG2pKGy;jQ8oSJRb}-eo2|nM(EPsAhVL6mW)eC z-wwBoL->K`$}~g#o$HeLz0dRZY8CFmkJvPAkbg%<@M%-@crWMt(cwJ53>{zb0qc=9 zbu@GXxF=2iNbt<_UZ=45iPWt<`By-I5v$Z5EUeDqLGVkyhA=g(Q-B^uRxC6k^T4Fj zw=pM#pC#`*K`w9~l$Z4z)#q@hU&`m@ek4Wxr!gb`z$Xp*QxW>2F6)hn|2z++X~941 z5?HT89w7Zf>=M6_>yq#ef9*4VE5-rdrLVwvuvsupC^~%1WzP4jFk)yHMa?Fdj7CTGHcX(gU zW6&J^(>9;;TXrjsn@zvNx&h|Jv5uf4*DdQR;NNi9Q*WJCI{*FF_&vcr^IAlog`Xk6 zl-RPj@v{g$5K}k8a^Dc^#NYurFZn!e{8%jEk98XKXaDo(9lsy$cIIp9hv6>iR4qLA z`|-216iXgLKQZEY1dTvZx53>89zA8r#~gJP;&*nJj}g~ooJk;M{25MJCv|7xf3|sU zgyMc&)<0t54epC)CP#6hcwV;LCocGJHj`BFZ&(p0kzI}AjSBbES!%$$@U7u_SpdJN zECG!DUj`o#(XsSjc%O}lBhRYOD{#p1zRRe~zyn=*J|Uhsm-`^#fng55@uyjO?>)}u zBuRp5y`aP`4-XVE52T~7-eE$BkB9Shnu_1dC9WTlw-Y}r{SELVbmfqB1K@DKNnOSf z&)397u}tHo*dcLKEb$F~F#EWn796XQg*UK4Abi0#mhb5r zULSFKV#}=Zv#q@Nq+{sIQP$qoo8hM`o;N1?w9_pNe!JD_#VXnfx2E`CB>6{)ZYO`B ztX=i9o74jYAF|#;;-oyr6C8o-^sV5B{!psVxvv6TpaaAf9^BKf7&|Ow%7);bIy|$< zbHpKR)%flW1<;%(HHhipMi7wG}w&#?pZ{>H|#{a ze9{;1`8{~MEON#85HCk+NAN!wvR%b_!9DJkiY4!_;y*GokoZqO(Hr`p^v9!GIP zWf6Tr>K8Hi-=f+F57@0C{%2Sx!0;H${&<9c(l@QAuU`2b%esl_)bINp>i=ucdLrh1 zqz>H9a!EeBA3nzI3!?es`e8Oy#Cz#uMXW291#Kbq0rDEw8T!piPe1ls+y_+Q9YNhy zyKakoy0Vl{=Ca*j9XNJ}`_oSA)UlI#Q)THJlx!b?`<|sv!+KBZH`G)AiasYU zpyz+UPfj0xB=^Z{?o%PIpvTYw&tyJ;`S4Ef6{Uiw;n|+>Htm`o(f^G4ZO;>z4FOQ_ zS@8yVP1|kayNQ?634h-LT_1xl@Zb4ly^ds?i4xp@K1u$&d7-}q7Vw|vBe>sE2Azo&o;LVVRt$TFcp`NP z^e1-E)tiyjjbiNP5PlH;na*JL!Z zHe%k@#n9Cf2Jc-`{7-G zleoVpIsklg-NO)Gg|BA9qkZNtgdaL@hzr;j4jS%6Tisqi3z-+%P;n$Y!u?I;8O$5& zn}o-U!GL+&k;4k^XPD{*^lL``!PVQqT80+`qX}yMD_EzQMJu z2Mp2wz-B0T2UipInG@XiLH{OscOiO$ep*lT1?!}~6+0q7b6a{#c(mG2p%32Ks`UB3 zfe+r6PV}g(|G|%WY*NSIx5#7qC;I3&@QCJWEi@^qe*G9wQeSPDD9#V@dW*ub3O8WES`r650pQAmk9plbHt0Ip!NPF1TRbrbvRA@0H0Xa-A)bl zJE;o`-YLtmzQvO-!@qZy@%8(_Z|>lM4W8@D{O>>f4_oB}oi}%NfHJ^8@P|q-f>+`r zILB{3Xv{4B#b!yI-wOCKTQc9HSYw|7Xu*G14vg*mlILvVx3nJ{!V}ABtF1ZibTXeu z*tkJFrhiAhfxf+_zgTbT(`dgDKP#|Dd*tav&7%N9#h#-3s=A!dilIN_KE$LcdWq+m z(6@K?t5o=zG+T94%}ZFCOQQ7gn1QS z`rlU1&HO^0L05qn%Vqd4cs>&Oc?1uz4sI=cP}PIs1N@oi7)DiIU^oxf=_++@tGqzY zvn%(hVbKMP5Zz+9pHu3U*`OHsL$#kyCeB0CTCVks?u%gRj!9UBT zmAo!^75s|>hTuJ;ASyhN#z8Hjo`_H_Bl5v^dpeczmiyW?R`L+byb{M=Shh*Kn-Uke z9j_^V!fgwwXI(@O0U-F7d`0pA_}wnY>&ENRWEq=1FYS&EKG8nW-~4ZJpB()M z!YRTp@?ozN9fe2AdLIf8(3p^Mb=29w{Ym_`{9f<_-1~wj>=2xt*v0S@jpYIDi2?t` zn>TL+|BVK5FV>gT*T!NQzM9~eI#Y#bbYpCZ`=tMsWw_r8-p$Ca#5O1ZiteWmN8V3; zAWakdrKfC`7Y@sTN)P@A{r<|pFB=3JOT3ch7@XHxv3qx)z@CX=1qc4)OwPNVQWqe; zQ753z#dYPZLlGXRaE}hC|EPW!KcA5g80z*#71y~Pe(d3a8TaQ4-U;7XHLviDV_t!& z=&F6zpX9=`Jse#u*P97{ARklwVxRkNrQW|&{N*p-YVa%Zoq63Ve~4q@Z}O4Y68B?y zE{o)y<#)Vp@QbV_mb$gUZgn~k0-tZz>6cMgfTm^<^Nj5OH?gcg0spKwytDhn#Ub;{ zHtQSF$M}Wx_2{3MYtMMh|7|)E+@o{4GJa|?_!;;$_%GZS%58sC*>~=}FZgFZV}(xW z(zq4d!~Y|BULxyv#WpAiMEH}e^ON|(pD&`$XwCwj^JU@p-e6_%Qh9C*>-?x+GH{M{ zf9U9c1^@d!bPDl*XT={93BiM`mk|7u&eg<1PfF|t5AGLS_0Vm?1JLIT9w4rp!lNT3 zPZJA#!9P5}>zdA8M5dqV>6sUui;nK-{0FIL-@x}!m;OC3V`>EwT#@_ee0x{ndvK>bqvq}yft5sy=r z{j6uBf5LSjkLISj4&uiS@gOUIE$0gi3hX}PJJd&5=0AiFD68QYdY?9B8~-?Bodfr! z!l!bcjH~#QCx3<>7KZhd`18!L&HEcf{S$D3j?-r)GRH7q=+1`WxtL?=x5g%x$67h? z+IiEid2rp4h4@&Ok-6c>5o@@Frt5`SU*a>2oF%F zl)5;&w_X=r)L9dugeT+Qu>OU(zOhmD@2H;yGOyHi+A^PI(rkp(S9!ni`abKJc&ox%|f29B>oW7 z$7er0LH)RTzmoj2( z0ZrzWx_kkyPM}+KlcAsb_9?jL?+}zd!5jLts{2u2miW&&zFk}Rfa4_={PSn1JoqOM zqyG+W3AZD`fBYIv$Ofh;gmcm)GOX3?@E7n!eT2AC=cV^B73{g#As{bHfLqwy*Ga^iVAV{~6CyRMf{%Z+{WH@zF;cp66Xq z)++?}$otgLS4$<2z;DnuQ~ZE>TO$4-I13#3pF+rjPs&Dh^2WIJUcDxE+u`5v!S=#X zA~x~;Z9|F!p5Y^do<|=KJg*Cm1osOHh3dIyG~644R$^-cyu=@PM2b+h?Pnw79QUof>0C;W^Ls3L4Fbx){&TTUhEL>v8Kt z>^^bF3V!)I@&PBhzta2Yj=m@E56d_7Sv-6|ykPx1x4HjP_S5cLiVcJ}`P{}vpSpmN z`|2&aaOX{lpX^WvYLdrrf2qu)+I3s}J(Y|&u=VQyhDaZ?UE+6Gshfqn9(|Am*d64P zQP_=zReWxTbvc=#zJxqk8je3JSxI@SM!ItOfm~(c`T9WLz6bI)PVtF2P2}zymY-RhoID{MXRJ_igH9^8Dk> znktUR{QWd|K%SdJ^sk;r?7wio9cAPXU-;h+WgOrGrC!Zd?7rZ9O80xk-$_hj_j~`v z5MqQUzJTz%!Us%q#Rt7X4!@YQ4G70Dzrf!*`aK`KqOT18iTfKNe$1aQY=!8cocvAi z5U(lki7sY+Xd$?-^LGk##i!Gj=z}o@u@JnhR)6@w?-4gy2PJryb#xq$`YQc$Ed59< z&r4}E0@2aj4;_I2BijA{HExPmURn<@U`PUZg~6!eDEM(f_UE} zWM0+h?0j3~z8`KS&4#RRdj!w0?fUh9`g|@tKsvm{5)XzwMIUU$1$+8piUoFJ8$4(k zi-)938CP^sV(F{P-@(w}zVmPEDgGhPot}w*Q2!Y_aM)@!>lS=Uo^L@Y?)QUAAFu{-L`OEGA{S2O2KSRFHe(>h8=sTXr>tpcqkk6AB(9dN*vMJ`c zXT%qd=7<-u;`8K-L|(Sh&+w5u>-_RAKVaU6f`9((**(c0Szfn@s4J0ASNKOyEHrg- z=3`)4UrIi)a!2;)eFTh?`Q&~Sb#NEonaBO_&@Ez{KV}^;_~-aN*(OTVWZp@$I`V#W z0XaeFEFCqAbd{4;Kgkoo5FR?P0sde#mc>!A2M67oEw zy_i0-VSSXWQ`7K3@Z;8mx}4l6C-pg=FY*>$5Xn6MjTclygFkK`sB0Aah&XPr=-|rY z-kIRvTWbaX-JhYu&)jcw8gdr=qnCcL;D43`f&abz0$Bp?{TDOvVzCTM&Hdi~UhWgD zw$nJUzjt4-j>`)lG||_JI%6NllKLU{Rka;{nM$3Ae9p=`6!3qqP6Q*a_2B~z|8B^A zq2Qe5x76)LM-_$O9~saN!TFLtQp9-iJj+n8eOi5VBMV-_x(NFRmX$8+58f@_IjT8`(5b6Ts#2p zzZCv~R|&qapB^)xmAX~RBb!|J+?;qJ_{R^Uo|XR9(dbE}4r&Pidg|Uzr=v#L@;!Lu zDU#^gNhgpeXI%Jlz}^Rr=!I~fa+w_K@ILvYru2g1>XPi&H-!J?R=p-1k}t`ToRdjB z1H5y47rItn(s3WG1o7XkbXTrS$ z_mXV0LQ?0R>ht7pi)Z>uxWglak9|O*hMebnp(eSGyItZ=$zI%TLe!Q#v-~yT-o@F% z3I8}0p88VV7u`qIF$C@-_D>bRks)IK5LCx=qWg)vn;ZHy%vSbuB>&3&h6j?{sQ>Em zEPKx5Msu9Y1l%5+u%T@}YivRtolo{t)iIxpe@HGkXQK0@JJ=0U=zZAblQ zw}U=|7kVg^cq`G|m{aTk<+m4}xKg9pY(G2*I+ z{gXNBqS>D{%YA2AMk)1^!28e&_nh02!t<(M&^6^GnZ+LWnx}`|KOX1xlt%`?r|yhv z>Ky-v@GpK%i2t4RI$hx%cQ_aR@n@F8Bmbz=#m@uRz#Y81@07Rl-?>MaQxoKOCMmj- zZbscSyx_P&2K=|cqv8b})kA}S@C#p&-{F49pVJ(N1bi;~{S)CI_;(!m@9XvGTj>qf zr&_NR++p93?5*_HDgn9_9vDG%DeHzEirr*yk)O!1BNPul6#l8BJtO=#sf%$)eP8ha zcFV|8mqfUwE4vXhv54?KeNfW~FB=963ioEIY{S~O{1E+(|3=$ESAIJDsCutksGVMN zD{f#1EX>ug0iE^0{bkm(ken67^^tt7QMex90p@*U7tCq=56DKAwE{dqAM3Z`7+;ZP#$FLuIa~@|u^lA+fIY`F~r5A`})@4`49=`|NPbT<#((^g>4#);( zD){2u>l{5BcMJH(9FQj_u4fM7e)}bkza)e16C`e3tHO1S`V4UWR*O0Z`UBd>k?rTd4`Q>VVfcul%w>Jaz5S;UISmD1wzkeO>j&bPB z*OC0*9NgpAHL}N&w0k}kt{J=8HLA~4U8d@@u%Cs0j&*Je@73>@{Qs6J{jGX2@O%4o z3f`&Xgtv+mT(jQ>DL5w{+x55I?J>(InL(S9_+cM7cwsi1&xL<}ANk{WV}{=vz`g2P zJQ7~3X+HtK6T|+6zUvWQ_xO?AU-bTeE+l!`f6@KME%6SSJtlyrI%ynmyOXMq&WQ(P zPulzG{rQHq>25;2jW{BHIQ^5t|85h4kR%;@Dyi2C|IF|0S9+iC>A*el;A8*DJ#;h< zA|KVV`Bm`!sLDrvXCFc5r9;-p%OSsW)K%L5%YE1Fc0U(SY<}!3`=C2OzdlNK!B>TU z(o_@SU+Z9q2Xc;NZXP6e|EYg_Q~2)_C$`u_gn7N&M(!sNL@VM-OyUZ(Xi3iS}J)BfLK;X&Y2P~O??O5VQ1Liq; z;(_qRVbI@4PYLhvTVPlMg_S^g4Jz z@YmzA!*mUd8jk+xDT>8oSGm~>;wjxt0fSy){Kr-_5^-IwHMK*NnAc-mmIAvMi;}aK^#(7RmRhS?UWHCnx`z zf>V93l+PirXC#?7h#im|6x=rh4>WkccmVyhug6u7BD-Kvj&|UMeaZouL}f-}?D>TH zMdp8Y^mqQsd^bmZIpvF+5&W0(KdFz1Y)Ii>{;2R@6un-=&Ze(IJa9|j*PzD{59s3XJM2?f-$(QnagV9| zfWGny4~TD5PnyC5>I<{1OZk+!Kj6B(3a`PFjHT|Tw~`x65HRwVajY+O1B7M3XF@#X z_;cYO^YC9}2hz55Rj>Ud@H)M&G0Vsr&NCMh(3L?)jB2p#SId*--fBZ8al2vk}qXd&(mVulVVO z;kv)^=Y2iT?^z^BXpMTp>YDyQyC*wB*U${}0n-EG3e8GVhvG-+0r{B1f^*EhlN8(! z$X5vegii(ue`h>ie%$-bq(XtQUq`)SYU>wc<_M0Q;l;*mW1 zn`E4t5$<^u2`}UgB=Ml()2Di0lZ_+%;}$rkLMV`@W2LU&jj3Wc-2Q?Kd?aJx^gXw& ziQJ!yN0507BEYVm9CAOvf1X!*dQbaY_y-2^5Dc5_bfxHrzvBne-w3jeGRar{7^&M*`HDPNgK;(hSW zTs%;gzh6ycr{jnEoAxI2gqMYT!lJc^JzxF|kZzja(HGe$Vy1`FrH279PA)pM5IaYrh6??`PP%_xv4r zCUfAQ?{9>E=wxairT0*4WeN{4XLypr16p_41>mT(k38|hq94FNX3dfz{-^kOeC7Y6 z_Y?BG)Dsh*s1fgS%!SKF|9A)w?C|$X?O)u|((}8HW!Lb#%6aDXyA5#*;nTsh9$cVz z<^d$OUoK7}d3DX{GvHtNLTle>j-C(12Sf61u@|lzKP-eR%pdB5u%DSHAbWa<-uKL( zL60o>ujVh96sdD`e9sbRpARf{K6Nme1*gObPSDzywZEMF{Q11JNYweUcmVr0v)KFX zZf{e#CoVmdr0u?@$HxSe{bhT3!dzFc_fY^E_*~)`$H?o`CifZt{^)2tL2h+BCtk9B zJZ*XOJ@7dW$gHunrTfk8ezi|Z&ZlJEn#@B*FWevJkF6jGavxQ9e z-pCe}zK3q4hYvPs$d3ET9$%kp`3Em5+-rX%;J(ajMzYbU;(&YOyeWTwk8Rly?z5d* zO?sa^K{J9=)OjYnx)7Y_dBQn9#ee6V4tPWH0`gFb7mx^)-sc!-&hK{gCoVYeH{c&& zS4rac>T`~!@Ef?tye`atOE~B4mEI@*zbf3%$yb5fvbCdK3c{@*ho_&dzs&Q4P1MYEsEq?#W=T<==A;)dAQ)Qw>t96Rbs zNIw(5qW^?~6X`d?;$!V|`eSbk@6IvTAAMK?JjqeLwj}*H2bj9QW;l$(2YhzE$ft|kxdz3JriG)2-^m{qt3w_f%Dff#WAkZ~N%K>fgsg%R#q_A(*9 z$jj(2NbdLhed3b|af!o;1@94pPLcE{7hds8=HH1AYW&mhq>O2`;spY7mfzE6kck5y zY%D?LO!9f85i{&~^f~@FIM(Z>*P+$E za8CI51L0o0gmv(tXP~x4*4UU8{z=q%cw|Cdc!mEl=YsP6G(SJUZ-j{&5!|Cs;H$8O z8TCuu-|<*HAEfV>v|l0#oWM2apU%Y5j){A;W)ic_mgM$PUZK5d)T$c>-U%+a%z#I264v&d(dwG^cZ{} zEYk7=l42`nsM0gRUQV3-*c*K;!q3TcYuEjzTE--t`Y8;7f#=D z+{29m$3cS)u8?mA3ycf2{QhZ zcpv?z%=I?-sgub&@IctXZHU4-%l?%8zmsP>*mcCo8jUE)UG7T8+4LJdUu!+B^nRl; z!cT}>@LA^B4>@zCEwYK-o)9lU-7Y6P@eb}qKirFVq;DLu@+0rLuUO{_|CF<}9r$NC zCgA=K)}^=i(mSsWhZ*u-rw=bbOz+7Kb?`tP%RM}F4;6=T=|0Sbm4{A9ly} z`ot$3dU}8(;o$?u#^RSJsvGpe|8x4@@!GGVUNt{tLvd@_*SL9|6g=Y}9Sh&6k$$>m zum{TDCtLB0KXT(F**`CnUi^Z%LW%s$U>Do#$#v}Xv4saHTgMKtmvL7s9zb2q|Av2f zAcY5rizA7b4#nB{lxtb$R9=UFFua@*Z~*VC}T%B8R z4xJUkv17r&?;dVTb$-iS_8iZP_apevn$4#9ZxO%!1?cA7rv~#Jd7kb1nyxbn1tXH< zmdi?VK3pX+xRIP!Q}8*-KeK(i3Ao1$puflb!Qi+F@6Mk{k|*MIiy@ z22#p@U-C4Zie>&9#T8t!=$tN+?~eEO03ZCFPSPR(!Qisysk zn&+GdKMRs<2+7U0PRFmMYiiIca#B(8!yhKPMtr9UqmxaZu5u5ivisqj9; z74+;AU@`yS`v&`#uzPe{$(F+K@lFCC93ICzhVzf;7r5i^%w|NG@7S+!UuW))y+hc3 z$Tj%q?}C5U`R5b~AB=?m3`JzY{iy7=Eq(+1Ht-MQy&-;5e3%HY{X}_3eutzZ@R8m( z)FWgy1Mac6%k+K%zu`w9^|?su(y5b>U7)@}B+Dy1L3PjYgW?+OGyEFN**f^&;`n0n zBDSYGr$FAU!NN!E0ru6g;sHAu{dL6G9fTnKi@z=MaKhZC>*Yc$@7?33r@ttCkla!yUeetJGl=m#pP?-2fr@^m6e7@(9?JxGH7 z#?5FNH2m>|eq`{EekU#%cUkUXCDHGO*UhB&kh)(9c6^@%t=l8sm)Bnh^?T&M4u$ji zV(_I$Vm80<@Bnd0c){;sHx->sa!EWDlE9r{-y#)LNfAF8IVZKiSJb<5656q!jU`TuB&S-ym z>PRd9g19#I0LMo0YWhLUgXAteu^gy-ZjKHok$6s7)y@A62jqME5Hx;56$h5&_bSJoL0~%})i+zAuHV?!LG(XJ{Ph=ioJq72Koseh1ug@0b86?ZD#oup# z-m@uq#tj6|75)oJ;z8}?zS%M8DakkZeAx${K)coR5QO7e@jT(bHG1B4I^WIRJ#@^8 zg_fDGXnRc(GY&r;x5o$kXzF|APj47U{~LH?ombJz;GVfH8_5R#TsOIHE<1jgxo$Rw z^e3a`H*o7F=6d{$IrJOCKe#&8HGb3~uis$1w$lx>QL8sw!Z~S-S9SpQ3OK)9ES_e1 zoUcusFGa5-`TKd^sPcSg&a|EezUMJKB)-ZqoXvK;j(*LMns?SfGVg|f7kVDGF1Z6u zRFb-%Sp@IY&o2`47l;4xWz`o)-fJH3>)aY}!~P*Z(-7XL(-$MNmps56gb$JjO#-4R zc%I9PsPG1-MVDx_sdtUg#$DWn*_WQt&^SwrY-e z{Dgr{X#e%$qI~9%oa=j0@6AVX4Zn0G{QHFU|0t~B z?q#MORv1qDtQ)}ctztN%pr_?`L=1^fi zk5c+X&l3Mlw$;x%As?0AYI1`T)vM9K6L;zJrB2wx1026`?C|r^Pp85;bJePR0ebaS ze5n2G4dGsMl#rF(7{NVia~{x>lv&Q@?-CA}VDDll%zR}JKsXUQfT&J(;4Hh#!bo6XJ?|@1{lHPwCG};VaT$mHp1P=KPnG zxv%gLdR_HADLChI=4kgFhXEp4K6^HmL_Ziyyh8p(6~8?m1#`8?pYuDN=Nu)<_)a}~cZmN9E;qubowjh#pPPf<%eW!^WF!7Y@j!#r z=NcZTthyCHOE%XGHy7Bqg`P5${Yy8vSqy8wm>vn_q zXYTPg@XM<6C^DY+*ekW=Z;vDQgZ0kAcaL~e6Vz_ERXC0w;Pnlz?+*UY7cY_r;GII= z1pQ7~!8}Oc^M+S)J~uqh@>g|K*nfoq|BMMQ^}gC`;1_#ieVrDQjhp1NC<(I^mvrx>ZLVrR;D=JvB5~ zy1h!Cdg^kiznO2)W1O6|tuNGk?UTET9 z$_|JpYeWJ33b#9VYw21CJISo4Ya<5Q$E#+Ycpp~?WV@#LIClPk?-Pj=!M?*+5_^@rmHgR&oDydZ zdY<^F^h~z{;L(%B$4*lCK=z36uY7~>Kf^J$;Gc(^2LB$lcbq&-?hQ~(3HE-Ujpbzj zqpyPWzWgNgzWT}1-AI0r`$=A}+85;Y$Zw22l4{N+`SIM}C?23~DzoTyaOzX=zLUECKh@$Kd{QqtR?i-!qT94$iM*M=6hR9le3q!ra$U zW62Z0z)i<*^INCnw>`YxrlBlB&+~rxKVc5!v;>X9c_xXw0FI;9_axpAs-IGv=zgQ| zJ$X~=8EBM<_mjJ9tC{f6_OAGVa+fUG|Zg zxb4WW?dI%TT|4S*zYcfNi{q5@nn%hXG2e_odnBCe=SbFnnOC^yehpG^kGTeK7|u~= z-TyiAyC^7RUMerQDe?SiPTYt%o<|)x<}XeOs5tEU)eo>Il$YbiDLzlW;AtRU4=)}E zu6MHMr?2>cxCM_(59o1Vc8tDXtN$Z;Htc-IKk5IfF93aS+}8g>?_VHXF?Zl!ddhK4 z-f+pm0L*<^P5C_Bidsq@05vy@SCe%;?}`88Z!Tl^GskMNNa-I{el?}8^=I%5{>fWU zCfEbFz<)itYi>Ds27N#s19N(*Yndw_ft{Ewl)s8Se&4BZpAF$xo?kE@!l)i*h~KJs zIB`^iKip%(JL1drW^0;&e~wQbCtLBW%@018!nfl0!hQC*{7Cp7HK+?gKf>1yT{E?W>y&yYJ#IN43cCXQW49G4 zlCu0&`SbY44TFDAIl3|M0QH*l1l$k3Gm`MUlU$%kgnO*I;oqb&JP!Qf3qxKmYY5lq z|EXS2{J9rDA&&>&$zI@d-VjfmJ^T6@yOChuV-KVE$uAt@Z(OhTA?q!D2JU;)cb4l8 z24If8@W0b~{mdE9dQ5WqY{4-vn*{7U=B%K<>4%tejuSYBH;h4Fb51DzoaO;x+lk+e z_tlT%i3<&I1jPqBPb$I#J=ML#7qo3`8Q#zLoD28drV96@HvTUYp=W(=b;3o_&iYO zmT`b`+z#8M@9W_I4}`^zQ*d144}try`7_Kz2j}ctaQ?vIC`e`m6pfzepCtQz!27-) zb6&6I9J~LxyR?-4LG^mXW2oaH&f4+U+_f?F%zoXipRf&(YXikCz$b3X++&|hKS&bZ zDS4cDUM1`Eqxd(xLOo1!6|?I|l5;J~NM9=c>nP$E z5BmMvDgGyR*kSxC?colL<5&JGPU|!Q|G!TnC4K1bjPNrod4GeWWUIPA=Gl|q9}K`5 z{egrx#*ug*G}agXM@{0%;Qs^q2ps;p;eJ{6H@9Wskie>x)Vx3Kf705{q5l*obl@I% z8cFWcrtm$)AaL0C*k4nN&#qefJN{Ghb?xKPNVvx>9Hi3cP3qRf7mB-~@0%GokLuXJU6fFdt|~Oqe#5FR;xD?;&$wB=A0#e zct$=~_B{6&e4DS!$@o~ZR;x7(_(vLeKs+`w*bC^>x$uvjdMNxeSAz4GEp`2!j_{9I zbvMJh_NDYWUbhS2AG+@g=cMxrN$nqpzs`Q_5VyNf!vK)~&biX39z3&8a@qB*`k6Dr z{d9~S4en>`OAqd$rJT6mh2iiG{ z#s@e60Uqc_>Y3nyF8e)Y-PCVq!S|v#87BAxa}4~1b*$=bc%1a%YJw#GcxuE0_57t| zJQ;LT@?EM2Oz1~PPjw}y#CuZaQIMA&M)F{&twZ6TKfmeNsr6VN16*Ner!_a(st-_MB$zE8nD zV`EB-zhCV~q}^OysDz?h^ih{V@B>ix-w_c)aGQ)1i+U`$7Iae$Iy35Z?&@bicwYX5+2D>HjAG z9Qa3&&v(~beDNTAtJP|G?6f-nqu-sep~3x5wSTaaP{&K)pE--1U%pR2E_Qa<*E~h| zoxEF{x%TkE9&|f3)bT7JB11m^c=`1@@;*~29K3Kp$FIQ7e}G+3`TOJ6E8G)@VQw7y zUU;S7$?tD1NeWjEyko!5BnbmZZ*MDL?9kVSIITl}Gfz~wM~}1a_@{2T7&z=x)Xb`7 zpE{nf>{D+4s`Q!sN6&tKie0MxO}=Epe!q%6Iu-sEKR17%@BQ?s>bK`(t9hrS{f@;0 z@Fh4$-}5@*9-ia;s^n{KJJ;rG*&m7WemtJgk2e#*>Bk}7e%4aQ(?>r=@_68Bgd~1X zyhQjHKY(lLaY=q|Tln9!(Fy;oYhjT~wMJ7s0Pp7(oDc9fi0`5A$!miDMx#*|FVMvo zJh(s2M>`(7f@O>;e@FWO{I8?-4~2iWRgCEWWdg3?|LSZ$^nv*?&{<3CLmIhYRheH) zUYYfzucVVof15Y56dcPQ;+oe{e{;j|xUX=|T)9qyAAz4$N>ZlQ1^ri8mpoR_9cGC*jMp@*(?zcuy5lX@`H!$zk6nfaQCsnzo!o-^AF8^ zzD604n3MDgB$_K%NbeK>8##CYdjb6p56qwczu5O%_**?Ie3!@HSmAqwf50{7s`NMU zJM?`O$B&!BJ?%?vhkZ|-@FK;YCoG+%;9op#k=j>04-c>(!98l-r1;geK{tedZmT!F z@LBW#v*$nK{Ha;^%zeVkhyF8|4+t8FFkSFI=t$!gX+x|cD%peB#3QICBp#q{ zF^}M$v7{sU378KbI_wPQ_&2fp*Wu}JQ}C~FPYV9ALkdG&Qu{c-11#?}!iU_x!XsIR zpB$2Mywj9*su6Dl?^iMuDCoc9;8z15OjrlWUKf82Tx*|L=FQ<98Vl|z7hDMcRXl-t zbXm@Ml<+`4B`=H}u(laP#16m@sq6sMWntN$`QbA80O5Z`{N2(=NPi~wdo#!XBcDfD zjJh1jxfkvU>$DAe-opb45>cke#bVHxUq6fXV+YQ$3pPFYuIx4Oz;rrs`1yRlsdxap zb}sz)e@|QqT%&KiWSoO<@K2b~+noykBL^?wW@Wry_-{x8)1f5yabdCi=K?9)ypZ`saXZ<`*!?zgN6`P6!yQX}z5uX}xE$vC!ht(@WZ}VKvFLYw z)#nT!qtA((&m8z>z0ps}4d}j~N)k9{e}eCv`3GQGoeR<73vqR=!`w7;4gJps3grLPFXE-Ihr>+L!)G4c7Cm_CN}_yWG*=VO ztuIe?jbCw?2-hTXQ@yU}wjJkPasEkNQhLf>b(dI{lIu&{mdssAuP9%XzyoKNC-6Yr z==bsTsXI~pC7`GHJK)Y!M??KJxTO4`4Nl3wF}Daj<9?SGKERKl&zw9y0YSz2F$bti zO3CMu@8){GC|8Al>XNYQJ@I@TQ~0FX7y4Z4E-aF~+_88-aom*W2?wl#d*)clzbB8^ z5dJN)kqP&}e#_G5%JR$|IFel;$-Y{6UWflo{y$}267ei}fV{#yp`HgV5Y6=n{?XUh z!zaW&2Jw^d32xNCNYY1A6OQ?~Ji-IL9)64PkKN`rQtJ7Y&rdIFpIQ8W{ycs_{0%hY z^gh~FhR9E_qie!Hw2J^pd1fnTNk{LIoXN?x9DSXcOG{DiUe<#CQ% zBPCAnun#Tq{Z@;3DfsVn`h{>$9(U%|9|P_KxaWH=Ja{L*bW-6SfTy>Zy8vMe_iUpq zfd|SG{SWS|^B9rbm!a@Zya8ON!uMR)+Aq2uJ;!cC$_~{v{3kxRVgCGO+lyCt9el$a z2XF)*5Ef|~;sTnBm4fdkbr>o3Jv>vUI!96Y9P~Z@axbBNpZzjRsN=2ddhoy5`*`Mw z`w=%^7XIHFk6Xe&VX-1O-E%}=q1(EF=6;IjMS%glm%wllbQ=lR@1@{LAT z`t$BQbr{ZF@aKUWS$}_?^hoLP18N*psn6RVETa> zfAK*=T>^3V&2YtoPrezFIOH>T)n8H_3@pGk@=|&Un9h3L|t@cc#TWnIgk=5sDVv(xm7xzZsQiOxX&`l7Z^~&mEr;LL=Shlu*hzw-${l4lX;drf{kej@CU@H>@X6t6C!=)CHhiHCXSb7-zYpf4KtD5qk6pY3%d z{Ih+TryDZDHm&$WG8$6QmW;3iaB~uHkGVdxNalQ%lI-7S4*XNk#(XUI05xC5^&b;g z_t^cct8>`<>c95V_W(!uGu$}go_P=Gcjhg2;bSD9SLLPHmTmA`&ApR8FCUY?1jnm= z>@RRlm=7MXSHThZh6lJ27RmQ$$ex8>r@C$s=g{YqRz8;O4hG=g5)WYi-U>GcKNp3$ z9c~c*J5Fvh7fbqxynchkV@jVcJaC$VcXmY-?&lPir}Whmr+Lk(et-PUHxB*Z+a#cg z{g2xJG~A5qm`UXI{o1GWHwmx8JNEnvI2=0kx$HUVce#JUy>RZ&+w+(!jc^{^CwScF z7@yll^@jvM6NV3*=QYMN65YGYsYlciV_`ftm zpU@9*sjv0t9BQ_ADM@%Asq^W(ZGX{T>=!39d(piwJ^K+?UXnd(@H22{^4Lpm$X@cq zsc=`(&w>5{<>vza0DO*Q{i(zAJJJJAb}RO@yB~JMGhTH`^9Auf=1p@0+21?pm8y=v z#E%ZV?`V?OBF}$BJUentdtIi-b?#_@FS^tP!6I8kx75eH1wUk5_fYS1@lwV{s^rtB zPc3{<=_h3NSh)8+)k}*9x)4CxORmo%4EsfLU3Lz3z-JlT0z2R{`OEMC*VNO(0|f*m zJh!B0@#6=4zXW+o*h5nJxCFmm^*9DSe|q|a_nQs+&#U^Nl841J&~R?>Ta>3W;-jC5 z!|x>Y?d40!edR4PB)o?~Z+DYw?g{pj&P$;`*DCJMyu831{_$A)UgvmgjxJ$W=5Qw? z@f`T)v}n!+VqQjf&@Y60_9uSBxOg{{CnLO5AN~Pm~#&= zP)EW0t`@Ul#S8Emet^5$QXT-lppC01N!e{K`+~S~h5tj<-|)GLYX{;3<`-wi5kH{; zk(!(FYQ06C`nI`Nqn-;MVD1KfDfRp4>CBSvhgY{GmzWRh;eio`gYXYM&V%v*@U-wR znwP)c9w3R|wZCadULY5PhwDbWLG{hvFCr{X9)vU#fo<_xZDZGzewczR^>$mSO zhWE4L6x@e*@v}_uo^CMTcb0ete0h{&e>X-|f6w(U_?11N`Id>|0t4b_Ur^_t)#`!zUiya0 z_!)V^482Nye>OU^EdFS>C%GhFGc(oo=cRCt-CTIycXT{Hl*HcghP(rMfI7;gM_oh~ z@0qRB>1c$6@18ai>LZux)C+<8_Ubd@CFZ{FBRsHMBj8oxKhImj|5*a621}oG-%|3U z`tq|8a?}CvdfdG6^#3-aVARW1^%k%_2BV^aNg57t4Q5{$w;2eBlY>Zb`DLBtkopF;s`T7=PG3ijYyoFJagC3 zH(TwmYw-$oBV}^atg~*%7Q1@XME_y;H*_8~(q%7>C3^($ydG~+=Yt=xwA55R?>c_+ z>qzwfjQ*Xt!aSaz$m?C`_gJ3`{sQqa=fU-AhxzpAeZ~&g!-a6Z%MX`iZek-`a2K$r z3+a9O8t@xZ@&XxlL`q)(@q6&E{%1@5U!Yzw1^@j{w@lWI@($jt#ETmpig<-`^nvAYdVhLA2nOy{uskEAwLBy z6sLYL!k$i%ivQ?ZWjgS{uI!z-8}8%C&jRm9%mQ&T%B%ab3+6NW+rfXS{RA&y=X~zr z0nE4Px5D*mpM!sHXBF2R(O7AT2hy(iM+<%lH)P@g{to#;`go|b3}1m?{spep_pZm` z2k@R=7Y`-m6==7l|CfHizm#3!ocq-k?lEg>;Vb_I{kGGHY&ILUc*kr#efoVP&{s*_ zUmouT%}1PPc!=pb6{SW@}7sB1RE#5?Go;-ar{+8AUeBZ+UJ+Z{| z|DXS4?j5+rJ}8{*aX#2#k9(F?cqi;ILf(q=7oU3`&xR+wKdRrRh+K1y^m9FaIK>~; z^V#TU@j=Ort|SXt$-MST0s~z`$A6dJbFKIVe-(crn+NsDni_Vpzk+%E414*CS$gZ6 zgnoL|%OUt*=k^^s>im{|apsIuJwE{>uGfb{0`JILje0x$f8yesSOfIwF9wmm{i5j5 zzZ+M&gWgQ|*KIM>^9s)q9w5wZkmupWOwYF&3+a)Ran^|ct?;`S;D3el>`yGXXRZs` zzjOt&|0pG{IBqp+=^BXV2MGV#Uy@xMo)jJ+pEyre%u2iS!6rQLF6%xw7s9*1z7yW{ zZ!5nSz&rL?H@@xP*7Kgnfo0pj>)swd{i*HWcW)~$2+rRn3~(&G`@ga;rS}OKRdSK- z%U<9--kQf>$n*S#@Na2YlpVkvqC6$8h*@M3;)6>~=4Fvz7*VH{Qcu9xekVSl?@IQ; z4e);|X_iIu5iU-MdP9ILZpWa>K#--Gq9i3tz6OU%!>YQNu zQF7|g=`+b|HOU_5RS5sAYhU;$f3qq4E5C;wumf+-B0PY3J3`7YaPYzw@us0CF32{V z2kM(it1w5`UzeHdd-ghJy|>r_``PGwCp;4ewcua&V8kyVyphK*!WZDfNkXp;1J3!G zW69BX*x$nal(}wS#V>?s|5f;c`uu;8oIKBl!auZT!2ehECi&qz@vCq1M$J<{p?=Ny zFx*rAK$0{i^^)-+a2lC?&QTh42M($FYvI31;Mg4aef-KpJ2D4{I?rYYd;cShfe3hKk8xNiMuN; z4Bl@ISVl?g9OIGfpUh(?;Qvs^^M7on*m%)$>Op#G8S0oaPztV$Lgpd3YcrFyX0>$Br9V;^bcv z;1v)2y`N6~bt(3A4F%=a)Agp}V8q==IHuwO!h~(fDqgql*E!#y5x_lWeUVVd51-{o z`gPDhNcIK&JaGm3fd}FR!dG5=fH^ueA9-Gf|3JK-bd&e!cm6z*`XT98aNmb7>{_xi z#SB1V@6R&}-cRY{u+;z4M^=gtc(qG-o%+zsilJ_3R+tO%UH1D)1mDoDm!4;wP&>RG ze?^#W5#A2JGDG46$QpL7t0Z$%-u1r%|JVoq?Rsl@0{Y)=0zBa!Gau=n)zBGWI@frW zKm9bGMDpM)$4MYAUM%{=6T=RA-+ST<+`f6_8iPNB2k0BfB(VdsEZ_(9d-OLW+c*{h ze*kj=9&o)j_Ja7Jr}|>~1uRsGURS@Pq0gW1<;_+A_XO&=o)RF8Um3+A8{qkiQ;5e= zA3bJXp1tG0>hk?{O&)$Bxe8#a>z^SWu2rTN=%Z*hc|W+v52)&;H0L8>4$ARl)J*o% zU7`~)CGLlsE-L(!?~cstYBs5lMZc4VY^LDdI3Fb)1eyx>_>;mrd9PkX--FZ4gL_{r z(EpLTy#@95VOw=zf%!MGtA&5m`+}=mi(aqi;DMty`v;uU-$woq$q$j76qJH z{^@y@?UZ|;VF4BJsz&OD^216SOKc%nI&uV(}R2AjCAPO z6K@-7t}1@FsnriXeLT#CBkv#A$e>WyAIbltpQ?Ty>{WQc+&xx54|=*buB89#2gns* zU6X{@eO$v+*j)Rkz~`|}>JNYi*l*c;tkp3vg`2! z_}w$v2ffV>|1}p_R_Xr>ee`_-{`r{;b3VK+-U#o;U+H%X|BK&qZtJhYuNL?Vl7koU zGWf6T0Axl(V8A|5ox6WOyp4X(9rz!QzrpW!7b!=X_)G2*_URaTku=(pu9*k6X^9&) zsqc5>2NnN<53a2CH$RnKU>td4@`%uTDSl8rPzo<_-IgRCFctm>Un(waud04c`-Aij z`>8sYs!_$8ft6F=7sTa>N42{#5TAO`?f3c?J`mr_EM@yk_W^X}6y*A-CGy!WriK>Gcu6K=F!zT`>L^>VS~^8^8mU zJyuO-?)L!j>?BIa~ z@k7ZRz20@;<|9Xp}S;TQM;9FxC0I+{#{bIjhh@Xzg?81i=XEgU7x)gQBA zkvGZD5buwby^kG0{(yWWc#r%O?g^hwv@feUHsr-lW-tbE^Qu2$EwFzN$MF*Smw&>S z^pis`(+9DPzFs@?`q{VS?<*gJT*GanpT~puM(N=LeP77|cE9-Ha~)j!+d$nub{zJ? zY4x~Y$NtHy*JI|2S9rL}Cr+tX4dA@LSdd?U2V@U$O**#iu@?-6kn9EK+zc#n0)CcD zFC-WG;5oUFoTq)aYtARcw|&$w5kH`+<3<1DW_aO#BtP|%zr@%%6Tm<5?|t#Wan3oN z$ZoIKcf}@s%c}kf_*(i(`hevq1A2ur%x^t* zXX9!46I$smc$|CcPRM(mI_!ek$*{2WrJ$F*`~%vBdPe%6c$VMS+^NYVpa;P@(J-A8 zS5^{xnseg9?(&&4^|)&||BX3c56oR1H;CK@?%+ReDb5>rLQi42h=0IxVILsB$Nxm) zuMkI|?g$IPk&mNoPyNX|Z9d=9m&i77$u4q)BF9hX_0G`e#4*zaJa7*?W9I08UO)hn zIqJgGCow*|{Cc22LOc+4ZW4972HYPX;aKzjP>(OWHefOD_#!|&2pclPW!GXDxa zG8Yc{bIijDw@ThG{A2gR1L2mZ-cB+bHEO~$RSd)h{dW6kv=qQUaS+7`x53A20Ulr< z4J$d#838e%PfLC$e-GNLMfKwr1^Ig6f0Rk`a%44cmwFv|iGRXBZb@JI ze+cmi|BC{D1>CC-)q#7=z#_epTzW0Tu13-(g1_o7b+H5erFc>IAJnfb(}0n#Bo~OE z<-*xgjkpfDgFnXL9X?E#%;rzTD&gKw_WoFGa3Vd(!jke9gJ1 ze+YjZ{qENNNoTdXuGN>laL)G@9}GI3j_d^V&*QL0guoBy$}fDtf!nT%FBGLE&oH78 z&rw&@C%oj&r{{5tTyox?qaCho&zW=2*k|cE^QQcvx)%1a&x;*0Pdc0eQ_$nPT02;Vn!U2Bl9 zB`%o#d`zCuUnGox3>Q82qj+I_$i7Be8<6%Ae<$@MQDwSS20uX=rXqWyw@L*0qz+_mMF@L!NmuJAuB76w1yn{SABf&Y&`4%J-x76nL= z`qy?l#~zkkye6snkLG|mcHIJ&NFHe4C%k|4r!tq+?@s7*m0mXw;tt1+bKbD*19)SH zyajXkkUKqoPfG;di$3CQ*KPfC4Q`oCJ4_{#%%i1~8A^B>T+4s~@8l19?8;!>s`CqW>2W zyaSV=@HxP-k)%u@3q0OreFOKUfBVEql5Y4f+^_A^X8jkuZX16aJacXyH#mZGrhrvA z=lI*DgmZK%I|(UT)HUs)Iv?u~#ATOEbuLa>CdfI8Bt_D;+P38H`94`fy&iVS_rgEs zZwLHe1@@hYJ|Rq5pm(kir=Ymr^%%$4wv*4;lDhh--wee$BPMd`k!3$@$5O)vbDFIV0Gbxa*m zIIsAvKkv_p=lnT)PV=AaIsYd5F}LTEHz{M8r*9^2t`Z*<4|Ldel1B^nA^br9O%0wn zhkv@1Vm}_2 z!VBRd>4w#cFZex@(8N^sLz_g8zY;Dn7Un1rk2(3*;U9J1SjIe%uW7Z=bJzit`HtCV z)OE=pV81o%O$+{W3Ty4%^e*9tkkH@S;sM$J)%PY2u-cy)=|8Tkc~qajs4XRfEVQ9bu$JvoTF!;&YW1fG&{U@Jj9!M?0E8uy4f+X(_{z0pgz3Imy`3Tyst`g-I!KKk|}Dze`+!@8forr!@y* zmpV3h!0i1QhcSU4Yl_Dp!B44mKbuj9wG!VK{^CXSEB_pJ0CYNq2UbX%Oda?a z4}fBL0R4~T^K*Csy18g?J+RE?kJ`S)X>Go`AZ{ZanADY2Q||m_>Nt zP0YTbJD0vmzG^5QAin5R zfjNex2Z+P?D-MT7`v33v#|{{gx4rTD>n-xe@y7U@8q@w>{O1Ml$2F!`a98S*>f%y2y?juF2z zoU7aKzylBI8Zs$%1>q9rAzS)+*yq>-sQujI57e5iR^WTe-xUv_cS=dsZN+`f@$C*^ zfd?J@kQD!kKCMX#{z;Q~*#UJDxE}nkqHv_wT5WM)ZI>RQ{K?@x9x4*%v#`_E5(r|OZJ^Wz5w}a%=J1jwub}v8N?Z|tqLO@#qXZtn9@N}`HS&)57pBcBI^q5K zX!#q-oc(3m$*-N8_0nIPUn}lyex1H2`yqKRo|jn_&!_K*Z<2Q+_$SWvPB_==5vM*E zrTrqZG;Q zou=TQW%&oK3(I?=^t?SmrAtNF5QpQ98UV@_+{^g-smkz8f{Qt%H>nHQfNbbIqGAy2m2#U7||Ec{36 zXP-Tb{NLc`=X1m7c<&v2^0zs5oO9R4@8W%v!%?RC8ti}7+5EWLi7tI&epH^XlKOj3 z;t%*0K6w&;z&{wq$LAwj7+z#p`k_VlBpLI>{!lM(9e>LKK2zNJZ7p$A)>Yz_NAdr`t9wUkWp(o}4 z^M_%F+sw1-rr>{)H);;Np0Xg|o_W}H&cSduF%LNBF5JY7Z%6uTS$=pR-fS{w!2coK zX1g7W4{VHu_+ma|Fwbomg$D3)f3AK)??Jn z^8oJ2S5M;AaFuP{lig4HtFr$y>eZCT)&9aRxH0@bf8pVQI)%`IIFt5~;sCAXvt!|( zyyaWM|1msK*#Q_FDgG{Da_ZR<&WFJ6IY`0lYQK`FrynUFUgoW)@GpNJIl}6d9?w(` zARb`5y*h8Y_^<=+Igf(88&dTp3Gb7?lu}QJzRuDglOM+|{MRSRk9xm-l00EvWYhhd zebWBudFGy^Pr9tX9_KiZC>{WBC*cS8!(aiz!GCcA!8}gEIq^fu5`XS-@|bYMFa`f| zM-%Ztmpl)41M$NM{`ZxKNRb>HpCwtkul+zgKy4ip2i_~bCLdD8uepAK9t!)BISY$H z0RO1NuGve-^B0ShIf2}s(lR#?bNAGdhh_Znk^HW~06Ua;UR7s|y-(b*F1rBwMbbZi zQuKsZ@^(wYZ{B=`d%hMp4>#xR0}tNO!(98DoGV!Af3(JCkljz-QFegrGQEE`V~!v8 zgXR2M;U7Pvh&PgJm_wQLJ#m`_`{xpV^j!GYJOvNFi$#Hd?=Cv_xg_e0z8C7)8HAyF z_;T?$lWaB{$oM{fRMb93&sF-2{kkOl6W5(3Jl>GMgd|>)rz^=yojhRz{;8*KrsU(z zQqFw-bdB4#WRT=9NAME<2F!ZJtnqWG+l$yCdDCJKROj>&H;8dKlOKS;HSoee_IrZ< zuh(0Xq@Dh{(EUd5W3SHbd+B@4=JHwho_()PUtL`p8H>T{0q`BBgGe%-;u-* zBA}nZgTU)pa{2^3lXrwB3~acvD!oQM683|WY@w%xcZUIEs8is!4=V{S#fL5Ife8Mq zI2HJx|Bv8=|0(~d+qmk7L*^O#tK=yZ7on<-i8N0 z@Ztk=&?PP}i8`1YUO(t|b*|(Av|i`^cxS?M3jWF4KS_T)=Kgd2qt?ZP`=c@I_iusK z0yqEWz<*ijbtlm~c;m;;CU!RU_~_?D@j$mv1m&^+p;l`&-G6tF`JP?(*ghuB8pQ*$ zf7#mM1;{e~xiIT0=KfKKORhV{Q&r`{cTAN%ep z`u{#SJ|(VsAG7`}(ZIWN_Nf8)xXHtG+ib%FQ^&b-E#^r$;`e%=2mkfu@i(#uh!0Bt zZxf$AYw*)?<6;VqaZft3@2NW&3HQKYD2dy>kp3TmAD+iQcwFJ!5wCOL-C)p54j0@H z{Co1p3;5*b41bubo7n3e#rG*oF2Mbj>@oHS_-ETL!WDlJ_q}lF^^*GIpu82tBe_bT z#Uq(-WsunSMd6S&1`|8#+Ir|S@xUsERjlmT`q{{}N8bf_pyC_wUn5_VvcC_BZzHkq zSwG>K0hS4N2;&p+^UXT@_PJ!IhkXtY@Vko?z0Z8}T==gy(C6SES~rsIZl?&?5e*32 z;}4M6J~8P3O#&JT`X6(<2fo37vn9OOYPC!}z#N=^mvq5)#Sif1BIS9-{ly3R{kk4C zYPFPW^hd=9e10L^tKSRUpZg1jRUn?w;)6Z7Cm&n)#2IJm!=q*(1qVr$F|g%mX16Z z>B3n`zMI!gC6{)x$KZSqK58NlYBg~FqZ_fkP8~b|E!NzR;5i$52OrRvQk^%8I<5SK zms9W#{^7SJH?pHHWdi<3`=jc)-IMSmW8O;t#66+T(*z{-?wJSw!0PwmiGR{L;d?%@ zKN6*J{GA^;|D@sp(f}s;5qm)X+vE5HYIWH^c8^iRFFia!Jo<(E@Afh4>%;@S&Dku) zFUV0;!ha_J206g+5dPusnq+Ib7|IT4;m4*`To8f=_lpzk#A;69;bElD!vB@I(%@g` zASC}kQ}6fN)|KUZ?qM)MOv0#tVWWTmE)puDUC6^#rl(KKRS#uX0e$7Lm6jawMD_&| z$q$b0;3~9D?sgQQU@I?W`K6-(8~y>X&#_FP6X7(2?{Qts_#rEYq{#DQf7kl1?^=6r>YiitFAM%pFW?ur---(v0Siofk$thI0NFFY26=q$z_;%!&fD8( zICJ6MVae}rYF%pSd;M(ugLwZ5yk0tgx7%BhUYw0b<@{Ibe)E8?9RK{@s#jhQ5CVBU z6Pzlt&^3d-z;%rM1$TkvE4};x;jD0aof`-rAifiCIA$9b^gn+Ay#dR78pfIY0dBq? z$!p=QLqj~5-&Q;u@*Eh~m+M*IX)!|JkAsQween3T!(%*w`@(|&FedTz< z@o(Udz;mP>Im;SbdUG8M_Q-oUFy^2enqO^IrNNHFBs)R2H}CL(shnr*2Y3ICcWF8l-kns85A(T%qQ>*@2m%%`C~H%^&f%g6H+oR|4L@tt+5&RBFk z^}7i7e7?ugRrjZrmiWG-QBhX?EyDkHr$>K@y9iyRj!#6_bzz4?^l{=n^(N}p{z4!B zB8xn+BK&*i)A$Q1^W^4p{t7-o-!Jb|SAZAz3vK=p{nh{85dNVt=-PZ^>~tb}wsdjq zn#qq_=1N#Y4}k~7%joB&rOY+tcnuuj7ZT|KWAJ~4^+HGD0pNEo9zfhF`}`?OYTdAR zfsN`6%Bzb&{4MKizcasqK8)h8?O#t>Cj$KbHK%(li+^g%f+zfg{SE$jmVkdA-%z+8 zFurBSV|d8#Q(hl568yl)_)h~_{KS~Q`VIXB*UazZuW2uyYu-W^z7#kQ;=L0ufyUNl z6$gcTcz)k=O!%iB65#g*^-6RQ^0EX!2922{e2)223-XUgPoF-G=zYK=lOLe{pdOir zfp;{*KhL4yxo#1^;Qet+^MU0L-bS~#`1=+QLU|vG7<~ceCi(uMM{ic2kcWrjFDDNC zD?fn;;0J2C{6Ld`r61Vt(g){=`$PD9`gr9I>k!}xhR*>Eah$N#kR=UW7rxooX+?N9 zZp!D9oavU|rSJmu{wxLe=taUU=+7GAYyb5z6J`9j@oUA$fQ3Kj!h4MqiQk%UT)*Fs zdY|W_*P-*S^cOt#6y6{nlcF1};+GTh0_7d?h5cIl2b*uh3-SyfUwCluOh7l3{-=C_ z{)ImEgnvQLbdI`$bbANxr9UX{^E~bf_qf@W!1@3zqcHH`zsr2xKp!-?c_IAc=Y;=z z1M(qr-+Y#N9t?Rv2Vf?j+0R(t?0+f!K)p_%PpUfK0Q}#o*BjIUbpM+_v+#d->UKi@ z-lZW`JYV~xmAL2m68Eoizs8RFwETHN{J%Pf&s!|+WhmUUK1$CrFOPQhIk;zET+WL) z0lYiHJ@9QYYSw^r*R!5QpMm4o(u?#f6UWmF_=k?Wm|P%jSdpzaU*ozh0UX>Lck&wd zFW?>EpQ|+Pf@A($y{`9b6iNg5r|&W^ao<$j5BLxKI`O|=XZ*q^aMM}94-jw2XTzG- zw|+go`8&op*C{^0IvjZ}yeqC)`~Z)c*X<=}FM0raApX(9qqT0I1#jYS>Ff1^8Y%OJ zx}EU|J?_`=jG=ToPr=_4r_p~h4}ZtaXNLHVo5{r!m^U+_UhtcoeYu2hcaPu4aymoR z5pa)%Ea8e{^u=kfXF0wWP1XKB((i!&-HN|Eo)s?xbw|#xsXn9vKUeNM0iN*#`2hR4isN!v%y-J?DI?9v?{hbr&vh(aLAaN`FWi&Ij)Z&l zqq(m=pJ!eimgm!qUx%;h*MCF!&(%*V{MV{k0RPavvH7?7HGH9t{dO~YulKtjf@kuC zF0f7E1@*@8C?P-if%uZ5Cw8?i5PCtCyj}hP9j;}_6ME!d;a_4_$-ZlOY!wB={Y!Wx zKkPF9XheNox?i7T;r?h)HU6I>>n?(K@ZWB)cZGY!KlL>Yna3mi*Z+q8DXefI{Db4J zy`S8|JroIe$96ro$^4>-4nrP?wdDQqS@=6N;2=fUn?f)Z=b>Z668ArF9^il6>8*Iy z)giBEehmsZ0+?lfU4y=0%Q53&Al#qKyXYW9(Vos z!a4Dqe)WWTtJ7X`!Jb3+@26Pmb^~`IxuE&y;`h^e?|u1+DeHr##C_F2h5H|V$bXJK z*JxDxiu?GdvYxGVOoe;!I*}h3WaDpzf5Nx&jf-!dsjJStw!pl1jqApMbw}X?46icp z=W7oBC;nyie1!i3f+_rympFJb_?n|HCaSBUuUl|c`5%^gn{+itJ)S&Z=F#H~ee8fEI;O16R=4HSt`Jf3eyeT}%4^*oS^uTzl)@U@PM^Tn4d4T*#iVjE`&;tJt?h$vy z|BqR(Ko)w1bze%yw&4HhDfuyZJ!#;Z$=B$DdA$z)@tdzK8yE{Ep{)=r_^tkI@}CAFevWN^<$>nAyj5CR7DF6>zjT0juh)gw!uR1R@BsT+pLJs``$TY? zrV7O^vtp_9N&j=KpHbGuz`y#TJ@aY}jtfga z%=|U(Gvc$JGk%Ka%zjz(o}NFW@c*{p9=U5hqUW3bbUmo9*KN1_S^~cxchj|mdFtaL z1@H99e3uH}#=MC=r~aPgLVAuq)uzP`b99jl_JXOseXP%U`U>PHaO=!#P2u#Zo7lWS(|KN5IF9cq%RAjlTC_j+FTRm}qqWQDv0E|=hv$4(&c^g(d0zZI%z>nbv z_?lr-#{W_m6fbxr`~&|o{>M>2Gt$q-kBmh}uQZ0o2Az#E74!M*uh*y9oACnuI2r)- z;lcMt@(V2zb|;>IKk>XSzv6ivdVEaY@ar1!5ex3e^V0Huq;Hsd@Wg;v@(lJRG01Xl z`i-p0Etz@WvRxc{!2724qw1d2A)Kq%Wmird{vpzL$oP_f7|K7G-)F-k_y9biT5o#r z&ngmWLfnUT!)weXwgcG~{ zSKSqpz5I8__$hUH#!~~~Px&I3ABBI~GLJM4X$v|I;Gg~=tP`Idt1bXQ3+mVpwBEk_ z0rjj>7hpO~RroK^H{=g$je1@9f0GXg|M-=G;y&wwpa-O1!g4uE$8Cf9qV{x{5P z<$7_m;9v1?$ouHy$CFuHv+t@NVBQTES>C?jzrQ;!=&QJBeodIk?C;y^i56?_W3=+#~Mi%|rY>_(Rv3;0LsB26zmQIge?493J!c z3+73De9&mrh5s2mLss*a?X&n8nJc%?>=X2$+-4xU4FgVnPTo^DfZr6<^HbL8rmizq9Up%&RNpeo zd)?vqf##dYq8lAX>ixLILiisHYV7loFnPPdiMA^Hus!9sB_qi98{_csOLuJ#eumM&B~?^U2?_B-r6fymSYL z6PdR-<~%I>l}pbS{#l1^EWa@LMXegp14K7vhtIP&_4kx3w^QnWzVy71?Uv|);Q?PmeK4?pHS4N^ zC*>vb1M-LNar#*Gn-XUb@dPEYd_KHBLH~2Pf6U*vh5u={*EUbW;|UYCwx@&33d5^T6I`oz73v`}V%p1x@IyMt56J$nUAQvp&DSF(TmN1=#nAI1AoYpA!DZuYS4} zw%iuF6ZKoSW&S|^i5^e7zhbU>ba2++);KTP8t-cv{S~h*`o$&c_4LPTym!198Sx+w{?*PXPyuXv(Gd?+&zC-+| zmVQC;g1=#{!5)lID{X| zPvKS%&7~+ma=_?)S2~>Rj6^zKHt!A^XzqS=FE8ioZxs1Ob zDzBk_kDft)fa;Iv2h0;5abMOv`zr|d&~f?}z5Gq4WA%r7_y+xQqbPqc8hKgzKc1JC z_3Pyqh}SQy`g5{s&4T;R7+oH}V18b$r&m~CjK++VzJuq)Z+JF-0HGg$p!73f>)b!{ zUjqA&fT#ar-)9;@KjIHslgUxQQV(f(`~>)XZE1V*#BE>?&rdJu{m6Zwf4c}+@o+u= zDSR%fZbCqle_+`q{1AF5`3mQSAIj*9)cKI#$Pe&>yP96f*nmj!e+a>sy;os9q}%SM z@(ja#P{nbh`cqb|MsTlvWo4nw>;n_-_Xw<>dF}AIB4C+@NnQ}a3A!Qq!IXJ{T` z;hynYC;EQ|{^lu`<wnv|E98VI0@+VkLE=$Wn51f;3?z1g>LHC64s@je=nTNZ^&w%Z;Bny=c5GNzK-|F zxp>m)mhl}v3GUyy2SA1Yl^*dOzrbm>^nSe7T}j{rtPeH7FPtNM{G9K-P`$_ZgL~E$ zVm>Orhl5JNKXjb_Uw5&^gemzS{BbV@_Y6BPe*oO+6XJDv2j`1FA|42T1YrDt>i$O> zzodmn7XC`zY+#A^{N1An-={1z5bnqNdvMQVKbA!}(t2c<+beDKOn(+&Z4k zxL=p$e;oZxTvz6}U&jdix{v4*x#zfm*T_Gx9mFu;2h=YnOC1mWLAFO8f?wkAE&aHJ zKl<-2^9S4G1gn0O1e~!he4a8Nk#a#fKdH>Culg&(iMtx$CF8LN=iH~h@ZRS8WaWnf zxX&BaN+3QHKZ=yUD|o@7)3FS}3ipA2Y{h+}dCh+i?n{2eI2cV!zPH9>DE$Jz7ZdIu z`SnqPAKM}3Jxs6iNeZY=bpYvh;2)e(f3~+-Z}3p~SG>3Mp+Nf% zW!I@M$q&>T!*ah^_+1w7gAn=>g&XA`vaDnHp7;S@U)BxTC!Kr&PeC9Q-sZ&l!1@@~ zy5f8Ia)y9p!Ts!2o+;iN1Wj3<_qt`jY2Y!HRbFcLlf7yUKgxd8;;)77G%V+k&eJ+Rn?!x^x zecrOrlX`f-zE zv|=8J|K!DO;hyyX*TFr{mGQBaT!xnyDOT}1-LmjnX7=)a8-M7SZ;ReO!>-|H1}Wp0 zam{#6)}~$J`I?oEmn9c%IbS5jRykoT>)4Lr_vm$wzD#h;_M7X9?{)S^af-k2Nw0bDjJvnS z_@>|8U;crHz3Sh4O!Xer}nUM&AeX#pw%xUmL8cS8Im-pOB@F4S50Q&H5Jpv;Jt*2lt=V zd0xW*_VzgUtS?lDuqv+haagj9v+wZ&_su=leJIz*dB0`$BYg!8;=9?WKG85w*k7K2 zDI6a%@daG(kro+__oO2U@5qmt#d`myN%$Q5I}9Cohqg@Ie!Bky1$Ku&=<Y_`fWE8o@c?f9$bG@Yodm z&-fqi?-qRXM_JsT?(-J9{U<4YfN{_X;J-nD4&Vp56rCWf5g)4@<1gkXeBT=MpziqI zvM+$&W1q>BxP|_RplGksk9d68OsKQ)STfEIf?iuY%#5i)C$1z{vxK4P?z-_rT z_(bNoZ+t&_P2f25x<4#4F^*dnzZ1RTyjUM+WU<^v`T&wE4fGpBf5?o)Mg2g+8+y|X z!@}X9oW637@dJ$R2<~s-9!fu;_$hu+&9fX7?-XU709^o~x*Y!mT>Js++UIrgf=Lsd zNBCf4l6c?l@cr{hJbi>NAS?f4*hh|W@u!6SmUN<}HFX8NWn2**m z^zC%{N4%%L*V%T&d;G^Fz2&yXbH?Ef*3W>yg6B2Id_1h`-tb@xkK_J=3-Gc4J}y%} zdn<(xFb;o6@B`&~J@|`WZ$1TgYc7ODe}@|s9@pZv_twGJMMKAe#UHfW#oVIri_ZxE z-TC+r$pusP-x~Y?=~O+QH|JXKx7W?NxWw~`7OT7nws5XmYaU?f(=io8-Cyg8>)2F= z$FP3Zr^yFp97k`HZOxcph2P=vu9UCiSQ79~|Ik#pkE)Mu*fl*Td)cg^GY<{-0Un;| zI!LRIJ=axh(uV>cpbdCp#B+1{KE!)99`Os@AL72b!k=TK^uM8S!Pho~ZJ&^<7^ryTd39$tjw1tJa5wM=yF(nD z3qMtaFN+_@ek#iS=FuxF!as2b+@tRiU&ZH-(Q^~woqwzOv zf#aR-nDrPrZ~BFOg3ZR+Nas)8Vms2eLpehC>((dAiCGda3*paO3Q%B|R$p>D_3fC#08}Wj{YCToqi}==(rC+NC z?%@OUdEyV~KN#7IaoJ__p1lyyLoek$#W<*aiI#ZCq2s^$fClr97`6$=`V&x5R!Hk^NeA zeQ_23#(BrpDt@o@2V?Rqt^<6{F|Q1Z+(aG7T%7>K01n~5m4LswQ{?R96t>_Ob=cz;ZV8J&!e0g~!zK5;4*h>r zTGbu&I(1>sx}S~bSeim6T zq;~r4hm2>wFUy60`AaOkmi|$24=%bY8mtb_H`(6jr(|C_@1-TiExjC(D}UzOrn8o2B@H{1x?sJ-dgVP*t4o!DDQR``N^T_c_A5te%?% z?^E(@;a|s!|Foe^h5r!ZiSR#yt|!tFh>!9s?H-@oOZMO=m1NKDQU+Mz-Ce?I&+Ua> za|Yd-*9TP`Ij{45x7=>n!F>%lx#ecn?TyEVd zrV0Gu+OGZit>>==U2ml^WFuG3tN;A}9F70(wA!KvoqY|-8@@XW_!T5&}>5orN^ zVgA;_b`sbFp%=Cw$Ecs0bylnXFWkDR>F#g(mW-Dxr;f--4 zY?zndt$?cy`YX+NpZBuM(HU$6avO)Kg^*mRg2tHvhWcL6%xM!W7mVs|5t|{KrXSdB+v7YU0rr+0n zlwYWbms~b$$gnNWat)be7B^q)-&64V4axwemHr--FX#0tIKF`#&=-yw z*YIgi9~bx{u5#L(U));ZdFvS6ZY&Fb8S?op{2KE={Vna&<{znjm| zmeS^Pm;aEBUnE%4k*SWK)4wi%q5Z}pf7fUp959}@a-QGo`+%;_b(7Av4LSn#zq$OP z@(4>GQI&O5Wz8e}B7Ftq_XG8_leXmv^IG&AWa)Em2>*&FiSSQ+weWA;$e}E-fUny< zuCodL_bOSx%;PDC%ni72-wEgA z_WeG7w`v{+@&V>ifOFlR0PcZ%C+>#b$$t!f8F&3IaiQ;t|50&A^YXoO4*PPBj>`PJ z?1lVVdK);D>uJIRMhX6s=QJg*!2fXsjAN$%fdAz3blT}8;M}+mxAC@@xotfQUbs&1 zZ_xY6>#-vW3E*hMIQFFg=c*6r_(1j84Zop$B5b&gGw?mP88)P^^Qny{>rMI+U&NCQ zw@SZa&u^sg@9v6QO_uxAne1wcA0U5TPL`=pAGzi9e1F9K!Vj>Hcc=6NE9>jx0n9^a z3jbRF4_)!%;X(u6Pu{})mu1<{f&2jB0Q~_!!2W%*?M^yU{D=ReN0j`3&edF0+?VAt z3;Bm;v-)OH)nmYOLzul+TB>9>*_ z(lHbENtzVJx0d6{tav5Myo7clKkz&|!{@i^jXL~3y-}l}A)LOBW0Cz(?vy$h@oUU@ z7rqVxM@OBHRoAj7I19KHLm`Amw-QYuL!X zGP=Gget`H<>K=+Cp7F0^)|og2-&>XY)C0Jm$Zmx!ar(LJeBSNI(!Rxh$H`^+~gi%Sdbq8pF`mueP?9Idop;3m1TZMB0KL9^i`D}@ha6hczsKeFJ!WBHzjdlLCvw`abJ`%E*e*stQA^fmJ^?zJ_K za4+so=Xc&G3;3MJJ(Y!C6y}StBU}XHzi^#0FAu+v%O8x#=-{lgF}_!8I)`XmOWNB5wvi8>hmC-L z$l%eQ_)i}?7XJ;d-NrhB!Lu(i!*2zgZ?Jx3fn6nz_1s1x+fG;QDtxae+)FRE_yP2k zX}WBex!=PSKVaUhHt_??;Nq9U|9ZFECA&XA800xR!ubXXNlM(u&opGq^&qg*UJu=m zx}f?;r9~>Uj(I%&fSnBL@N|%fKP>Uc z>uJf`iL3lRJbLP}3~wFZqE0hU!RsjYiU05u@^R1aQir*df`8fwG6&un&gHx?OrGEm zm``!fv0e;c*GsXqIZaYtZ+Evx`W??d+4c2am+N7FCgLC0hwL!_pZHI~z3K#d9ltoX zT=z;(_tRdc3*`^Ie_6a+8)aQYdg-uG9Alge4EP@~ATVqt~1Gw-g@0e2D`KUhp3?aDSU{(bqA{Kn;ZdgM(%* z1^*1UlBXZf^1H%6U+2O79@|us$5Y2APcq<}(;29~|LPTfK(Dhvh44>3u<6MA)rXfp z@%!Arw&FhSBMaca3ohdm^A*!<@B`-Ih&XB?;=C|MbG!Z>QkA*&NJdxz2Hdc{^vMh2GVZEZ zTKp3_!ZQ7bZF4b@caXQNdd_D$$C8iG_qZCCIffU6RpbZqn6PTH{1x|Qqtbv+-iVvP zXJT%I&ARfU8}WLz$@l^PT8}BYg&&ymIq<)}zE0eCTNwj7A^xk*pz+@pPs0*#kA=qu z8)Sj!8&xKV1oCe4#z8A$p?jsRLr8SomtF_%M+6#=C0tI0Kl%>);ve`~`nxUohmUcr z^Gx~PNA!W}n6#n;4tXCu%epb2vp0~FCcLlTYlCyv-rf#Tz|R+UI*NV%Nkc2 zkLb@CxWBw`_#OIkvX<3({_s_nr8@T18S(!O@V^C|8V3AB2OElS;54_yd%{FdxNn>~ z^$S^ejgjrL1E}MD>uW;TOij|2ln=if`0y`?Bg2v+@U6 zgJqcn_;T2|b}hzG`T^$aVvmm}pM+Jn+U<5u+-kbYHkX$YaF2g0WXIE@ElZjEI064t z`X-BJ)9KE|1By;}J%RUo7S>3?`?SOT1OMF~ycb)`(GxC)^W@=q z0?yIl(d&r!L*g_202u@*fgk9)^*N>$uITTG*XRebIp2excqSWTmnj#t!?FYa)Aax! z0k3Tb?on%u$CCd#u5TG#`Z;`OS^5rkwVr)j#7)1Ubv@$sutB`W55&!CPQ6KZlx{9O!neRVmiSQ~ zBXp$T4*U_e_uj&F4F+ZKzTnaD!ResbWF?K>y@&JE>jeESx(IU0T!IW^;3 z_&j>*t#}>Y*R!|6b>$ECv)G~U{1|(eb`kI&?vnq^jQjxOz4Qyz^{}_(=k$B{hZFfV zcy+^pbM>v@-?nD(+f;rKy;}GmBGBu;35(7E@i(fku@6^6y1VxClRgK(_TYF=^D>C%dp{OgRdIbl{Xl#kefNcqSNI;g z@AmMY6a5|eRKx9uJ@S$wD9=BjjD*k6 zTg*2|Bloe}L22TC)ArI$x!<%IgMUYTD}#T*2k0l1r5`<7@DES$ihq>Z`vE?X6+gB- zR(eMuZnDlyTH>F)C0&K*!;c(Rd|j`1HaGDCc2)U`a8KRY#ecBGocTg2@t^cP zSKiO^0eycy1?TY7^X@!#pd;n~@UBcEqIPwaN5yAsa3 zCHhrQ`1>W*4>&%rda%O}Oz3}+-UzJP(jD*v#p}FdzQTEm_YXP$WzEy};Gg>ck^BPw zUAo39;p14gTCLV}KWJ|o#?7!%YaW(@(04Z{DFt~DQ_TrOf0zn@g;rSjCRjNx&iHhgL#9;n7?8JMdSmlsM!*B)4LRAHfs(9d5%xdc#u}q~QSk zjO# zN2KsvbUkds`m>Bb)}hNX`hBtFfkSsYz51Ly7JIXJ1wOFksb#+0V_!db^bS7*{@qRk zzazb!=@bL(Uc1w|MpqAgzr~RU-=tl}5yuI06Y+Uu-7M^5Nz|}=_{B`0@AlZ&fxmD5 ztXAS3z4m+I-Z4=a-A{g^(C@(8#-4c3`10U<7yo(${&yHR9LJpjL@4gs&%OHNI35m& z%U?wD{qYzca9i~~;h*KX+Ftl40pfmKjelmomg>7F+n0p@lsp3dgzm>WL)_Q(_tX#I z*F14pw$$CR8utm$0lDN@et>pZ4=?NY zxJtnd@>olGe7e2;q44$hf4{zMSCjL?Z35m~U1wyqPgTkL58$cG$@#KB>3p(uCd&rt zdA~#&IS*LcaXtz32QZ(ZZRitvlJrCUCJ2e z*z<;d7K?vDE<<0m%cYJ9&RZ0`lT}m3|I+eY^?rkiEcmbG2IuARV|DUF`~mt$r53?I zdQVx`;BD!k%6H%eVS{BVCZK-Pyh)##EC50`-K;*R&ulAfaDNAO3;zJ2m-T*JzE8f< z4cE;U{;3|X`|Df>`%#ALoNhg`KP5OCn$JQ9et;hc9g7Z0yzW%k$IHO~X}5dL!^P{t zJ@I!YOId9tcI*w-AJ1dQ;BTgd0r$j(G3UGK;`V#|ca#0fyKXbx^e6MKzn*Ras{(sH zgU34d$F9GCm)uP{l^Xkou|FQTKMr@@U1J&toX_1Rx}na8pXGd#8`Pbm<-EH11Ni~< z$2;m9(w_r7z75Y}Jfa5-N?s2Scr(yf#r+zCbLLM_u#jb;1lgk_`Zun|%e5MHHO6;Q zd|zmslQ#^7`?r5lF|wL>s`$=h|4}>`Jthb5x6PTCE5iNgWd6PE@W&GW@(=Z}%euDk zF~1jfR5unbIn0IM*soTI-{^k$BjWf%lln6Fr@S=t((#l}J`MXUw2+&xWHW<>FHoT+ zzN5R=Wskp~#`62FU3*Xb!c?e(($~ZIZR2-#IgO!oeq^8{;eAZq)O?YAu5oI=u%F|9 zvF7vX2!8u{yzL;$=1#axThS=q_N>ndu(_^oXB2f`&=rPv-%g z(?$}7`$E^D{>@@i&hJrP=BRU{hYa<;^zM{;_&WR5S@d*#2n9qiJt4f&Lu& zV}=1_PeWRA-V*1{&#_DNqjl3I zyTouW+)}crI2xARBH?HTz8A@pC$f%ytbm2*w`HNHEzjrr;uF|kG#Dq~{}3LW^1X!N zZgSq7=YIBx^5?ali|`IjYMbTwpH24irN!ohCOQOsgaMI)f2QY8X$HnFIgaHukUAarR19e^*-kO5~Wlp)j|6Y4Hk*_bh2>ZvdOVEcLw{ zykGDCviaYHd*V$d%djYa7ukE8^W3CAY%2VByR6rY#r?Mu_&~Ec$SwG%E_jsOwb#kJ ziWEP98#^rh0gborZs=&e``8JcQzSg{1I+>X&nl6Q2Lr!2{OS|Bfy3Mg6hO zLNkodtyadmEc6Q#ALp`!3G_bfka5rZ6KX}uuyJ^J$)=L6y{mU#o?d4i=M z>>#;qZ`QN1)}Os8za{IrpGk?2?Gxd@c$E)i8|X?N-K^0d-cw)4?=J-EZw>w_UZdX) z$k+FVBoM+q`eh-DDTI5jV<;W3OMY&@GGEQ|{Lp?C_t4+b^TQt7JL3oZp69xV{=1`2 zx97++%$bw9tbZ|I@mSg=XYvR5$%O#^OWhv)^D{3E_-8&y+kS5DupUldmV9g7+=2AvJI{G}J1t<0jya}B!(!XjRWrGN=UB91|_#gfG&%*a?#`p&Rt;y_t zzvkAM4#R%X9{rqotjwo_N3T0@KcBC!C-n12?azT_nVuH5&UH`2p)@iNEmqnJmi&JtF=uLk}jxKeQj66aE9Sb=XFgJeInL^bPQ{ zLD+4j8_8de6ZC?-^b4H#EQKeK=4P^ljf&e$HcfwASn#Gi*63BOZYKGxg}4O&n^V?tqTd4D=_FFN}nL{3+|on5(r)<&A{- z4fw%Kc97Sr!aM5}xa79Esye3?j=Re96Rl5mjrUo1dNk(!o77`g40u1DF#mw{geWf$ z?d^DjesOGC;>3~n6XpgE+@lW+h`;m&(8me>n-F$zzk9^E5&o4A2=A{l?#R>dguJUR z-0S>g;b-^-`xW7o3< zf!0$5|4g4g3gr3lroQ+-JdV1b{k#WVGoRZ#$f{2G$K6cL9s3C~dS>whQ|3j2|En6m zukk+`CCtAf?dszP*19^MOOgJV&SqR=zijS1-e67offL?cHj~9qPW(l8u~K8)G2d<^ zJz4X2NiZyZ_~`t7**^YNb~^9%%mvH*9rBqa^G>5V{N69cMbfT`TMCQn<4VuFMc!@+ zmv!aI#C7ID4-9yB#4n2--)gVe#rU73gC&c0@vXNH-%1ygMfKsOi|#DHo0YawuM5}0 zB|KpfSPk{P_{W~(^Huyu%HN}N&GkEj96$bByClCY{IhIyi+KOrN~PYw{%7ZSuHfB4 z#0|UzKh;a5L$JOtxIfSB%}V?H`Ev*EwGO6(4=@UZ_f?+HP?q6amp|Zq21I{euV)E< zK>JmJrw!7#9yq5yO26i&+hCe@HQlrueQr#$X*Z4UtQv4eI+7*V!I@siQf`&qgm(?n z>-M_Faf*L%EY#+R%jk@UDLjCEVyYhevmMc~hc|fdoa~D7Qr;KuapD)_><_%V2@NcH z$(VoQE!UKjqkFJ~*Ael_ay~z6{?eo!@c^!~lXU8hTE%tJ4rQ6Q z$Ok&&P2m47Wwr%_E~sIjqEjFPlsbj<0k8UFqe=fmlrB)})74r7T|Ys`W85+?p~7QH zI6i7N8wos~w&r>Qt_4FKQ?9}v5XWC;eZ7v{FcR)52i7g?ha663b3^|hebKos?d%6W zcU$M_(ECchzsr6$gS-Jg*`Jm?M0tO+RwaJ#^LthMlz5I!o|>n8&AsG6_ElyNqV#_7 zAD_^!<1Cij0UxkmaX!uv|GmZ)R`XpW_y_h=;h(a+7yjGp^Gx_Z91=IcKY!l@|EfRa z5#68u_56-91H(v>e*Db?DLi$(8%yhb~9sNFmQkU;Y0iyw)Btu-sy$IxBXpy zm*;2D2M-Qsv&j0By;2 zd$v6)b^2o_{-?z2?_Pp?>fN~U0^Hp0A$SYV=FR@da(q1dA+yx;C$k~e+#Y0AK2M*2 zk=OM)!{4V^)ek*-AG)IE)$APPRps@&(EOqK3cU`$q&R+xM8-Z%zRH_*lN{Km@QR7- zIA*#00Dfy6pXv3ZBXba+Xx>945BTsy`fb3yaW0nepM2fJBPMr%X(S|=~;WW{QX6HmUQbN{wH&mI64FWf5kY=vi-Y>?VA2n23iA^O|g42S?zXeydD)N8c(f{m<}# zq?1+fY~cQIp;`C$oOu5*QYWC?I1&E0<+sYdf%SE#zJgk{5~yDe@t=YIV*1(W+KDcpH1dLumF32$Y*$3M__kG_Wj1@_Whv)7sCCzbQ0n|@r3vvwZG07{8R6nPl^9mkyo;K zHC#O&o-q+W06+4foacFRBK-4fMpo<0;s@UCoQ%AEH{I?h_<;L275>#v1-?JvHVy6d_z^gIF5J_;5)E-) zenhyRkblTB&9M?c4WIUCAoHKbk9qug{5X7UYKQQJ@KKe#(c({t^E0CV2JYY(l)vU7 z_3g!^HhRfTi5yD<(+KUXHa_uwUt{9kz2apmV( z=BWoDySyd*YaX;=-TAjpzrnim>1W)Asb`-FXje0^&m`+=4uyZw&G1>gMgJE2&Y9b7 zh^gI)>_=5^*j@GMmU!P`otER2I4M3~;<|Wbm_N6R-um3h&W6{~9{IZP zHGyZFhkmzp3_o}nc3E%lFZx{82d{fSA*2bnt@e9==byy=VOFolC;myjIT(az<|*w? z%J-kBuj;iWFXwnXEy{i#WH}5x&pq&BIxLmix6r{6C8f z(oM+YX`3p9{~CEtQpWXuU$))jd%*d8``X`u`(H>u2mM?%>k;Dz(BrC#|0RC|=PX-y z;K2VN$Ir_TWIs3Ql3mjIne%pu_Rbk<-rs7zmBEr<%yo=xHxd4UXBGaB9(a(;-mBLu zmOKErbK>y_(2J(0j`3!_-}m5N{Ld5r>-?RQ-q)ZX3@bb&_ygsGivR1~;=d~XU)p9} zckoa8Ys3QvMw_a-<&cQ@Bx70iSG z3;rXxr#-DH+@GwjuZK_lr^Fp2{14&J;p6ZT^dN?h{71%|Y{!rM2jm4mnhp2`=gaTo zciTDDI!v}i432~q0wHCnnCbYPb+)b~~iornmh9?g^_~tR^vOK1_#cCh1l=p{6 z^lMTNtRN5u@UOl+@qfOiY44lQhFP_m-naJ&Z+XJ{JjhNy+;^<&&w5z+fok)(CHy}p zzC`w&loy1yN7;vdpu(pD~`X1 zK2$w;N6*i(OSrGDL*K{WOf8l$O+C>pf#b22Wg|@CUu_EH{eMgQNoK%1^Xp68^H>z8 z(fQeDkGda^p$Y%jIsHL;+eePL02}i(0Rk_cbm)~48(mN zOAh|;p<_=h@xNNFH7)B?GVf?JVf|X#&N?Z6fON_^Ec?2i1T5)8C9v-w;V=si;%DHz z9v_&USK!<1v92fo64uSYEf0kI1srAy&VkcZ@sx1_-=?pIX%o^lJWX}^7qcW!(_ ze{EhTpZO|zNMC<7A3d2)v==S8I)Cc!r)wgJJw8JY$>B9=LyI`v3YeeB+4s z=seG`-SxJ6l0HF?YI^X0z=oy{|G+%`LdTBvb>tT~tpWIdLOh{P81}h+sv|IOi25LX z0_d?0wcv}+d+e)F>ivv=?7Y*N3-2u3(U0`^3@P*n>iqh>0M79n;6G6R!>_PE&v?vz zmveY?wJz3mdW&T_l|1)pN`x)ka@qaD@z90XJ;aQbmnBc(mxmpcf5Zt#JAbu%@ zzc&W%yk8>DR1@lqz%rxGe;z-WCB%Qij2Hf4M+SSo$8Ar9d;Z)?_aooqczMu-SAc)M zwr{~VehnQUUDi6^rOj)+FCAb;Utp^KzyWrZKBSi7yOEx!`3>_P{myoEa!B0*yf+&B z9lD?LBE?-`c}ciyO^zBKoVF0QWZ&|zKQS9S^0yz1v0n4=kB(?XMGTG zU%YHIIKIa^KZo(2zX#npGVDXOaQaj&WxZY^8^OMBIPx z@2*`7&)hTOlW_k`>+ZW}=@V$?SQfeYrToF<2!9FwCzJ13_scy|JFjf53O{=mp0scM z^ppGwdFkAs8{ijD@q3(4y9@u=1s?SEG2fp)pFkeK{OqZCKn(C{@Grk3{5PBEmEa$^ zOkd-l=gu+1uM6+Lnla!4mT9&vFCCEa99Vx@^JX3Uxr#4h-=%$MBp#rBJ%xXU>4_o! zmvQ7`An)gSkO!pcwRMO7uW_8PE*Ix{V!*xgM2mPymrZ7TYt@#kr z`B%$%Dd_1%PJV&a_gdoY_Q^>mT&{H4Sp(dceDz}wzGLdZajz(T3fLq1+)O8YO!)^J zKW5n14IY5Jb|^dH#Dsrn&s_M=JiHmJI0&vk;`ZVX@FxR!FvlIjjBKm*J@5OEi0Ac4 zzA(YDM)?`><+d$+c%z~?kAKemy>J)* zR1JT0_u8D7$MQIgj%zippM7cgx+C*t@&LVbp0KY4b!;yyp6Xbqo4)CmXPn2KMbuO`7!%kH z=cVa5_=7#_9_t2Of<9{WfWOT9BOj2SApB3^DFHsfvV#R)fjH0eCf>vEM)9Vn-|1x` z{Ieh5`{8Q1B3(PM?z?euEd0-@*Oj=Z;N#funRQs(;{W6|9q|8`{6AdikN7^;$?u}W z5&!WA^lgEE^qXx9{+XsE{4e2ULZHr189p!LJ=ZJS>CXS8d>^_na%YpXBmN2h=aw?otulv7b7f5g&pJQ6?QHt&T^vDVK($B>2j~EaMcs>pB6du95 z7f$h`?}@j=gM3QVf86cjH$QbB4~Kjo$Bh4h@aP?mns`s#tyyr7d*})O zh4ex2FZ@~F*Zy+y7phMLaNTade@A&k@w{H;ef&!KxsS{J$M_unK9&!ATxDWpy4n2OLNdWTTA2(9-fAsebsfWtq4SbMQ0RJ-l z5%)4M;2(NbO};b_=zlMgFYN>R&~jVuZ$teXzmVbZ9mjmE;@Jm-tFFYyo_&1S$NsU| z=l-97|9zGZKbE{io7z+_N8k z7(7j|)J1X~ue3WS4t%qGYZTsB+C?r)AM*>@yiu=%|7WKc78VTr;tlwSIWS)#kB^D} zd)vxSng2T;k&j?m{<&kx1H`w;|99x08b$CAj~mMm^vExDOn*T)-1cQ3l?VST>mBm_ zaHaL#eCokHahm#HO8kGF_v4lNDf?0z&v+L8A-uvreHZ9|M0^H9r2aRacguZs;q$E9 z>(24q8^S;Bh0(A+Uw6H`9+$$>_DPlp`h40vDB(Zj@k{sHPEo#y|6K|V%Jb2`sQ07r z0UY6;KAN&_U+Uf9d_L`TgmbleeqI@zQR4_tzG^fT;r@_rp(2d87xga1#tALRJ~T-M$e{u#FivebQg!ae%sar{S%4mZ~Kc4GKp z!hb*ZT)%9=^;;~rZ&<$X!JGI4_X$7o>6CxrAH$~;5+(nS@zZt}U6k*K_a9mEdB)Qe z9k4TKo<8NI^Gycmf9mUL)`b5m|HS*N)i;geeKqT6#QQ%|1}Wk{`9B^Z>_zsYMGmY9 z|5fl5AK3?*Cu6@fU&6zW@B?4ghxBK%KM%{~jT83ekiMRR`}Tb7!ejTiDtxeCPMJqM zvHMdJQ_Fn2dAHk5%6T_MPx!~bs6T%{uho7g`~&x#D7;U8C4HWCxf%^{_H;(VV-L+! z{6m%;f^UGCf^+rx$P!Nb@&^nXQy%{?9t;1he>^RD0P)YE2k^WH{+sj}FdU~?Xv(qh zKOzB?e_+{*A;bxhWyaL@I7ct69q5dH~omH5m&JvrH)8uAX{*vCJ8)rJ6=uk05L zH|i3}7vvjbTlV`$Z$FPaU3ioH0Dj(s`<<1Q`M;KV0lFFZ*Zyf8ul;zL5QOe`g`XXR z|0}HDcFjrmhySDZiw|_A`-AUx0{+J|P;T~z-)e#9?@*SbG`02z-wAU%uxI90u0@x-FvX?_TC{%nQBMDd>G zbDJstfbmNIZUWwk6X3qvB@Zv-eomgBE&;Ey52y3t|J&2s{J3?U>AnjU9Ka@FJVrklK>aJPiEI=(s00(AiIkT&KVfW#BH~6%2Z4)mF=KivI0bw3`CZU<@yll z%_?oQRMhQLDvAqNA9vuSC#MSnE^fzbash%yN0|^V&`t9Eo~_(l1HIZT7P&vx^Ssad zvDUH{+|!>=o(dd_KXP!|>9jk-d8_;H%F~@2ai4>9d%s`Dl6S-Jg6pzG|Gq)rE$bX` z{06tv1#A^3^Lym73FoUme)4?bT#xb#_V_7dV}m6v3t4=WtT_B!}{k3JakdEvc{vuFQS_;)YVqyye#|Jf>^Ut_;V9V5HZ z2Gd^MAm49N>Qwe^omC{>e3XgE*hRT5(sx zR-ZqB2d;1)>__ddl)L~BTvt9E9=HyVy%e|ObzWz8+sZcbo#b=qq;7ZeofFm5UwgaZq~p>*IWCp>KBadgFZ8><+0jB;cMhPDi*`pI#uZKAnHt7yirJ=wmGBRg2`= zl>fue?o!tn2>%*CIsaq0Wp*W;Y|1!>>_ai>ntcQ_z$%6jfH>i zmkZ?m6lW{%-|loemVGR6Guj{Gb@r)QH{gHm8*~r*)AlCcejC|ehWK@e|Nq&B)*q_+ z06;AG2Ul(TU+MQJju-x!ztD-~-69k90{M5YXW{VwJiiJ4|0ndNFI|=QgZJ?J9rUfG z?vMYPC?1ebFz~<q|@ay&R zV34B&7>2~@`8mdC1Al*x;WrfSvCQ9%uR?pQQxvJ+Ex!q@W2Aa*#8MY~YJL&lSl_T4 z;sNNqC)}gwYqG%BdF)KWfMr&H<(YkVd$4(I?P;po_J;s`u>9d`1j~daFnQC zwvPb)4n(l@45FFZ!_2c9H$N8e}!?S-w*kmaM6SJ zE`JA4^hd*r7fhP*y~O9<%Q7sM!v6||V(|_7WvK7vy1{+Y#vS~{R_Ae%We?Wk$MIst z3m+TudW%WEWj1kdrQJ$48Mc|l&$r<}w`I4`?MsgZR+)4@<7Mu_Ke$i*l`q8y&+!j0 z3;)YeSvNZ#+{^!)&(j|j!v8kuzB1o2JDS%e+?T9#AzP$r&9g5j!*CebmsfqihJ7TkpaDL4ySXQ#qHaetQJ$BX{zD!8ZZ zWVP;xcChScSs1zj^_QA#8{zE+_JDkw(ff$=Vgmkw&jz2jzeXdR@Xx-w9pQe&g<0^< zeeXKz0BNI9@cUi$0|xwiJl%jdl0Efz%ikB@zu%1HBRutRS=w})`1@|k-Fm8Tq ze`fwGddDX}i?0CxjQ}2d1>LrCa=T8+Hp zU*{?5inhU)%*P=bpdTc<=d_3g-*sDJl9(MG>(!bbF=#wFyNQHmm`b_!rf&~u}aEI(CZZ_(VS+T-D z-!J_0F;TjGwlO20AKoMUCJqSiRre7*_jr85ePqE0*)wODenpDeoG3pr7<;GS?UqW3vQXJ7GGmVBTr z>#MB?{J-#KJ`dLqRx|J)uOp*-vMj$|V7J8+dEe|1{>?4>b0Z&0$3DEx>FSFC{0}*A z&%W8c0{?P1*>6*4OTd}dO$+4H2LL#LAI2qhcXJc@Hwy3p17{hjv%%j^{#Jb=2LCU4 zBm6i1GxZE`Ek7dsXZ1Sx_ro9jp#DSfKjblwop^v@Eq$Q4jDPnP7n?!Y#|^fHfB2x| z_Ved{AKWXi;jxPMbbf^QnV~O8pP!I-fJcYen}pFF@O_hTil`ri^Smm&|HVQhEnd9_ z-__;!X#=X_fRr1}>#PreGNnF%o<3DS6Z5p0KS*8x$HlAU2PdopLOy_eka%F7{ee8| zp3HFL?jLgI0Zo`smHapST}YS@u#Rqs%nKy%HUvR_E?`3|*wjyug|6f^ueWO}m1O03qdSDG*0WbW7u(GJ^4F3f$;P0b&fbp^6 z(mW-c*vM0pGOlK_mA?V^^MBu)3jfHbn&thq8hv>3|K)H95ByYmQgMLlW3n2*R(!w) z6UyuJz0c!)aPGlBa*Vh?zKZ|m_4w*jbX_=SxSomD14R#vmhiyOYJHx^FOt{zxKTWB zSch)78krZ*dU@-CdAsDd;Cx3_i?l20h70Tj1QexmTqxgoGHRo=Y@=zCs_I&PYf0w zRUYx;@=wGm;Qr)H&E!|i7W%iyuOwH_45sU{=7q+r z{3A!aPki6=*W(o~i~Z=s^?Dt>&c5~1zt|o2r60>qX}}f!spDnfANZpmz&-PE8u6}| z%_6wxGEU85zCR-o4c_*FbKMbF<9<8By~Yn-mU(zc4_U$z1K!EEl-T{3epst;zbsZ( za&S-DrV##X=t@7#2U*rAg?r$&^w4E#|=2ygCYHgSk{L=D;{8!s{h~` zZ38pmpXHRMvZ}kt#yIIF^e<6=SyCUkicFjY`kxq%V-FA1SI;ye^8^&<~H zK7;T8kJUNm{jWoWhIM$>Pg%!@K3VdkGq3e^aPN-105~f=cD7h=1@J#1ZzKHE4!QBuVohInR1l@B;C4v%-IdKS2L8Kae;g{|WxAo1+728G4jFAnO%OWm5#c z^8LuisdT|gS}(cwI=Ug*~ z;jZTSC5Q1Y^iZ>h$svzJarHiK|0PfSOggS(D*sAd6~Brsk=;A{7WWgqUCfr>81i^d z>nLG0&s4ZyE_vR>_kE2_nGi7zoEFk z4KRdv`dXV7TP$1XdPlus)Q^fkvUDZx=F}O-rwR2&=I1T!UUGAd@t)sIZnCbHjqq1f zan5dXP5EWa1cUXF-cs0%>v%Xq_FV?3~4cwl|OIOgB0_@|zS2U5n7*LuP7 zbMzB34;FpkKRk<0@yu_g{dMeE|0K_IYndO(_+O9jux^6#rtA;WVjVYGwwW#h>nt^! z>of6yXrtl*@QMz24VkxL!M|ulxB&-ac;Hp(`v4EH&(Ihi_^E~;dBC4gC-B0*@TNR~ z{Fm|oTaJP!c;~#f!Tpa|r!^7o(GT7HuMKm-$^yIp^g5Q$8&U$KHvz=U@GjT52HrMsQ>~^xv z^U8c-x7D}ciTD{Wr^5d#FXX~Iy4&@0;(Fp5aL+PqM*g1p3?;a)Ue6EBq55)!`{`hG z+a2bI=)=^Kcc0DhJJjQ=xB^_C{zqrsVypLKEB>N>S1jLe=G5~~ZdZBy>h+v_Uj2uQ zb>W#ZXUBm5JqA?%*bI@AO;}*4`KpQX0<=XMS>iliAAr9#LmcoXdN86t7|*RhKYzh< zCf|4!9w{UFhdufY;H4V{H^k6aMEFcz-_l=5zZsVEV?MFHsr{U!4>YgFNVjO+KzOV< z{dQZrK=F~R%|kVh7&uk)h{fZ=KcD-Lk?*B#LHW+BvoU?k*rqm)X8tnr#Cz~B8b%-d zY67n~<{2}**bl(`O8VQd9zut@W_%~S^T!2!q?(7#7jevbEZwDe0Qt$jAztf*wmtiX zFis1{x~yGz3?6um`vecfS3#8%540cv@c{kAJ`fMEK6I?~Ke}J|mu^uWpusx(^arKX zVP`J2>EZ?AL5_jhD)|5Fbh-9(ga2o^r8(;YP*$2^f4V{*#mN5ySWx~`^Z@G$B(H$0 zCA{!TE#-5-zv6tWzMko{DIS3SM;81qm)&l_4oM`D4`7{d>Q!%OpOBLEcnte}5I^Jp z>px&zqW_Usi`Wk5$s?6m@@Y#KS?7Yc6zr`)V@T*Z&9n&!RzJ}0!ldMc24}q@gnWA)9|r_7w}`h^&dlTqxjqK@i#Mo zvK2qpdX!jrgmDAztGWV~@|6+(3td0>r>&rnC13PM^nd$=1$c#j+L+Xzw@F!Dc5B@- zKFH@ut~u=4*I)k?e6Y1az$5$_ifeflgj1KznGmBsCvyhyg^%HI|F_G$)? z6n=ZQAU}+izEAqGpdYG%zwb91S*G~Dx}V^F{;Z!>{(e~2rSm-lB%z+xo2|EU>S?4a zzp+@YM}*(qN2fg3^{fYxE1ri>@&mWmJ0jnKrEb)6d(`ot70lPQ;GT7Uv8?CuL!-Pt z_2+n)5MK1Ncx>oesIA1~aO~dfbj>gv*I8F88OCGsB+YnCeL%n)hC}@KGQvN&k69qE zh%OupZ;`%S4<7mYmT)hf7qIzX{yvOENdR(q-@jDE18j*9v@RpK7az2`lZpp$|6cJlbmGMW{M|S*Z)RORy3H28nwSnzIsieKo1 z`=3I$HR*tFC+PnQ|J*m?0{m7VUGOUD*lHb5^2vShPai-wP+hP2-Ke3we~o@s^ULr? zHDB?3U>0XK0Iw{{>Lojprlm3$a*URrPio{OA# zn)}*Iz$-e3_&Q!}aRY^a%3y`~;RFJZ?V{7oR=B9=o?M9^t545d2_K*E+~aS}miOfC zf17O0a723lZ5}(Hzlk0&4!uD*-3Q;BteZIj_nXi_#RK?-o`nZE|I|~*E6deJ0RLr{ zQm^-0-}d|D@g3*O^Th9Wd9LCy`d7xfjuRSNlIwYu?~h+5*BRcWDdO)6&fkE0jThk` znl8Y9!F2hi@K3xPh5ziYz*~{;s7?(2cj~noc|F_D_`&brt)8=VK0m@e`RNVeUV7J~ z_u-F*L+>-smAIP`&i-C;Ki>mi^y}C?>T>vhx5xTLYi`fqbiIF`$+BGYNIW3@k)sEo zufFiFzQ%ZHV9~D*!f`w#-kgSG@J-`FIQEBTi}?50AJ_Dp;f0>+r~Do_KNkLHRs0`_ z@4?G%xFNlYKfWhKZSbAU`peN+nISq6`pih9l7UBWY4c?Q-qaV8G z=nEhZKwp4Q3;J$6$IU5BtN4K7XXytxC2?UC?=wI&Z|58O#N+>-{8W7L9RHId0r#V_ zP&@$6(-fS)qJA^w1E2@+?_Qx!HW&VJL#e~>!xM#7e@}PG`W)e0qelEge{MtlDysJ} z@dez!QCnGI{k?d8c<@ynzyCIFqXhqLqV( z(gEVv9E)sFerv%((^L>eM*8(*5CkTS*B~v!Y5po z(dU9UaBsJotS2qI$~eGoihjZGW>*aj*d~Pr@dEy~!aaHOEN=NNcw;kyfANrbK)OLZ z0F66cM~6S>IOaE5jL^E*i~<$l5gMan`BdEvH6 z_iQ-yf2Y%F=T)A6cp&`K#z=k7_1XSsApB<}HVpVDPk6?F|2E^sl2@mEPhE{Tn*2t!2&VdhiFEdS8o#fp}sG59NG5_j#f3TU$UBoX1*gLcE}PMZV$z`Q^a8 z$h8(ipLx<8NAUj%IP92D!UxKu#!LPJp7mm>FR)%?XMMfx z(E(x1`_KW!WHuB2k-anJ19*3o4rnXC0RGXz;GaGLemwO+&+u}p2OwNz8Ta%HVM+Mt zc<|lwwjHj^Mjxzas?u!%NXFMlM{IMV( zBTJiH)58bItD5jnUVq@|6N2|o!~+@p;^^y<9}4H)8(G#U74Ng&L>X9r*Ywt#q3zoy zVI4m3PnfV0wwTw7@C@Ldy7{*r%VX<#)x$^x3qR6rUig$y;#lj~T5yg|tMcpLXx%1` zmt~D(GHkxxN-CVQZY!^kj`ThU{u$@kQ?18IoIzfrqtDg)Qi0dG4@(DrmrL^T=#Zxm zdX=8%c@-5Oz%#smQ|n8~@8f@K@k-nxKd$=Rmg+QE>UXnAB!7?JDZ+Jkh2i3r*Uz}l zWILyhw|WM=Z;$$0Ccnd^Vg69KKcT@Xxs#(Zi+*y)7L0?ALkX~M+er9_2TpVH{VeZN z)%UY{cA&VQ+r0|@i39MH#QpH^2>riHx^b3}S8sJXElVDad=2wr@UH`Sg?LzTM80RQ zQ4eU@o9tkg>bMb9!QHN`oSEq zUcXC->oq1A2q(naAnbwPV;VPI3dx?HlkiVDxzP8`iWlG&^uynMKgrP_%I_rP5A`t-Js_R# znK#K}i8=d@%bHK(rL!BJeUPhpmp$`gxF9}^!~@i&oou^Jy#f5USx{K`@9_)I{sBC$ ziSUnGX1@mf|9ZQst5GKO=C$}rm&PRWZLg?qro5o~Y-PJRe(8d_#vA-VpLJTgmY zaI~emnQ-5nlb5FN&1B490gsF`{J#hP@FmBpdttA1#Rt^OloxTt(HciNmj0}z@Xzuc z*fW|2p-{#~K*ZSUlFnt%8HTs$BhmxF)hmB9ZtqxuC`gl%|y9162T9%yXQ0sXA8 zD*V$HP>=mU-j8}q-|VtI_C`*BPosgKq`&u+c-5j~nOC-MsCTKJo>K>F@RMJ2mrv=( zbJzUkmwStrtSP=(L;YVvH?2ANfPVF*bV8LEB@YMRlnHn+ENBKkZc@e=C9Lel^_AeB zdLZ~E-X|T8--id_H{l<*Hj@tEG+Ob{?-vM(pn1SAvtBQ8Nw>p*!gBu#gDwyc8ZrRr zI%B{gVGMZ*{4{+~4$JMV@LLc^IB@L7ldkYicvg!C;h_0<;NT$c!S`$CR@hr(;6(UL z&odl^J@TZy%N?|Z-+?2KF(gZw93a=F2M)9@x#fFiJWdPm2}jtQYwMl1^Z+mcc10TGjdXaeG=jWn}@BnoT@&fE5LRnCJ&}@G9 zCwSl&$WCwBCqQ~5`9(a};(g{9;Z>a1liBxD@Bhh zg9qTdarL?AKF8-Uj`9DFeLsbJM?WupHsBuErEb%`t?_|vb2=*)|JnSvW#PeB13w}> zYh6yKI`~%6(s6-q7yc&`{IGa|Mt4`$#g{YrRcN~K*Yhj7Klpj@&wBl`%YN$7+vu58 zd_kUX#V~(XMi1EHT!%V6?xU{I5c+XvJY(ME zZrm9jto|_B&2RLyA0^{6vL6=z(&qw?Uyh%wzW9R`dpWs0*kk_iW%rR+ogG`t&=Z%# zN5l(tf6ZP7o{RjZyG*)wlrz77h5Mg?f7)~AIXEEBPG$RB@KuHXtd1S%zQ*Hxz-1m7 z7=XM_?=SeD2>-2CryUO6q3Q#Q~-eSs@|_CujAfj?pXkYV1?fNX;~ z(Dj2y+7{B91?Qx{+a5b?WHk?-)$i>O%q{YZ-H2s5=e6WkR2*Q;E%QaM*DDOiy1#+n z_t_sV9{7Vcab6hM!8!^4eBci#Kg>PfDBeO(pfB)~=!!{nPCT$N9*+~{2RfY%@P7lH zb?TTmIiuk;hX#Qw(S6ZC>lt|~7;0pyc-VN$>YSmHo<06t({Ce;nrTFXC~f1pmF zd_VaBl}jo;fF5z|Z^PSr_Lt@tozCpk{4%~lT|zu?4jDfd&KZ>^(vMT!cW}%wWd2Qf zj^8)@t6dsWsMo)GJLPrF$Gg^$eHB{c@4~D2zi4k%dAovi&e(%@h7I_4 z+gbJ;-?Ujw!8@aozmpC~^}6E--Xs49AEZ|KfBI&;>VGuIh*zMmm4q`OobUP_yHiI8 z;PrRlTWo&glt7g6$oe>0-I7n&{f_8W{C*=|HJ5)*Jegd!A2B;E+K=q}Oe>%HkKFt9 zzgpc3ANh~)XI*#Mf3(6nefFCBC{tfKJW%zAq6Nj1NK=~DDJ@I8vFJpNibEa1PP<5YN7zEgN7tTg#8b8E=D49P9~1&XRnz`yi| zelLHS+%~t6n^|%@;_q44XJBsEve6(6+<<9RhY!%1D>?J4n~X1b;1lS;Bv<1Hk%Ha_tQX+@+>CLq`dy2H1ATn24VZrm z{?8S?@g$-HgbV2b(gfuH!mCqoAl!=|Jbqt&de}GUbL?9DKZmTZoxdI5eDlqus+Ujy z&2A-a#h2iJi%!K8t2mvXT(~6wPOyY)g_VEje){X~dh7dsUv_Zv^p6f5eZu^kKtDYF z@>5yb@oL1~^vkfm1@ZTEbo+&PJ^A!>)@>!MlLPM?;Wv73a%@?qY5u9Z#Bf|V*2`F1 zYt-GP96oSHc8%+-_yHW-PvHlMBFBzy_e{=wTZZ9^1NZp1(%rQ?xTU%*a>?Z6;~E*d z`mVW4*{>7tc;ViAa821(dLDEWh=Fg0<08k;cmIw)KwhuoU>*FxI%0qF`2CVVTDbS* z>yv)iCfsf0)cvacw(n|w&j|G&c9>Sv5&r47s3+3<8^o2&=bz5M?wj$ZgDoNPE4hA^k@!RLGve=rCKj*Xh-ziI196L35ru1szxw~$TC z{1%AkMqcOTbuzA*_elPtGiyiksvU^l!2_KM;|Ko5O?!)-FN-Z5vyS$}!UOufWxW;f z?{oM-^E2~mJ>`k)ue#4l_F;$@7$@U+!CgQOj6M5MqL*ft{h!biF~4B6?{iN)K>cED z&;?!YpP|o&by#NVx0h}S=L43!7RRI&e8g6|1KuFdRZ^cj$8}YFFe(rR;t$5T@DKgJ zU*VnrO?7?x^=BTQm=A}eK;Iwh-p)VL~sZh#Jd z9l6!>ST55vdd@aM&`%U&ovYN7dTW8{!0Rq z$ohHEa1ky!*3Icv>*y@>y{xOFbxyq2*Jv#!cE@TxjfTVj5^sZh*C(u4arfQbs_uYg z-QI)bzPU>`s5ke+9p!5R_?HX-*MzIHUe1+}E+NnFwkOO(lAj;*pRnz=c|557xLquk zT?6is0r-2<|M8hdYVRf*#*IMTkGw;}><8xe9AvSd)5k}Dp8OHQz@tlnOWogytdrSp zhpWkD@`=lw{V<24KKo+kAHn-;7Q9nVG7cTUX{?C{8jVI7-uLe>4_Vrkm`$t*orA0XOhjjp}{NQS%ECcm~Zfku#NB39pB=~~|GU1M7tqO|#4m14n}c2mhSX$YF-b;AA@d3j80o0Z@dm+C0~Qjv+5_>X@&@FzI>C zU*J5j^drIJJQvz?#!hhpaFwNRaf5i_!w!LA{z;;~LMJ}xF!0TX{z8Xg9@v+X@qX%9 z=edKwvh4f7*UsPp`U}tx@ByFSGZ*4{Xkwx|8}Y%!u>U0aarUJm{|*ko`}uyK`vm@l zi-dW4{M|}r`E^2`ftSfMU}>-t56sI(Dt$07(OuY4@uL;IJM(^v6r7(^{3pCuc{}QU zxT_lTd)}BR&ISKGPM3iHs$R=J8=U?E9q@JrfD-h7#!pVXuJr&D;`oD%aZW!>tya$x z`d`@RtxUiN^_iX{Z>D`uJosRJo0Gun_`lN77hC1AiMtu@S>CswBVP);lQZvqab)2G z@t%&EUbnR17`Ybgr~VS{F*AQDe8Ab-h%M38*h_84IQ-Oqiahe+bN4A9*9$w?XZ^x6 zuYOS0j0gAA!9m@F_jx}x*!u(4iT3yHef&Yp@0;)zY^qr0Zh<)Sx|zUJY$yM})LSts62_Hmov z80viZ_4S-QKf_}Z!2gIq$J6IJBW^IvfBDsVtCbV?_ZzEq3;ubI#t{clKbk2HfL43L zzv?j&{PQ(6^XvG&;+uHc{5r$K$7}I&o%y5TX1tb`^gn`s!UIs@=m$_7kPMn@Kd*2? zKlohuZ=*{iet%4U47?w6b*8e3_p${I#euqDF(IA>|G=iIE3!SF^6_+8u`e3<#}Zxm z1N`nJpvR$sy15dS_!y|KbpT;<&Cu;sj^N zYYLyjKm6YF%x4(k2Ln7nnh*T@8$5RBsB7@?9J{N$TsYzc#znMO6Z(qC$6w7q)HqG( zD*`WB@=5%Fada9#a36Hg0m47u=PmocaGS;n$1MtZ;sNRfKDiLV*MxmyBkRedBY0i7 ztSpTz(hI^-f?g<6;)!1-zj#*EjQvIO3*KInZMA3U3HtWAVUfPQ>2UC4;)XZ$`2pKn z%&?Am{)O@9-T4K6L$=X)eyr!J`F+G$6ZwDq+(iDrUM}Yr{4bdwh5siH$iTZopFBUH zf2&SBY_^jt6p%;Q>qVOVFyR%=LzJzNkFfN`)U$f#DqY;K3;(2-N`rqS0L11y?Ngo$ zetF4*v#f(595`@KJ#Hra!x!M+eMRedD({f^S4k$U@*e2#w@?$0TN#fSbrdB2I}nA0ik!{k0Pd?P^z0IOPl zKi(lbt7QTRgW%2j$FL6En>!mC@q$kT`xGnrEA7Ih_s@r)KzxsU*eji=6 zn(v$aB?35Myuk;|m$A&xFEd9S9k<_!;Q#g4+h=p;yOsSkb(izY12+El;2yrODes4$ zF#aPQlL!0AeWZMv`E_`oX@{x#wdug?#{4>dG-1ox9(n-O_prO94Oq|6@3J04-@yaOe`B#rctH5~{5a}| zq+e#}fKTG->6;0C1gs0(5&qF@i^zJ;q7~0N@1$!Y_~-aJ{#6$j4}8Y}%Haj}Q(uY? zls`l#yi?UTD_gKnd_vu!FxX#?Zo~WRCr=+>L4NU-gquiT9Q@Ze+wsfv=#Nhm`eSNYqjc28l;85`=dxd05q>D& zl(7f}dJsPu3+=Ng9zcfma(ICCx!{4EynL~h!v|G7fMuRvC$KISI_@B{KHhLt7Q(yg zrtx!g_4^6_f5%+Svb1LQ0d zpSXwleQ0Ht-%svOOXsk6DZ8W=J`fKicVj_ylYD_;o^GkUVV`kXv()vFiM<3p^YlUT zyq*969oMD&O6a?O2B3t0VArtw$v(Gb)$iMV^y(iaqNQb!{X==Tmrb5y@QSlt_(uHjN`H5e@?IC zu;6y-hy$SMnI|7mD89qe=eusn3y_{7FKG72cSTEoUcJHo@$~Pgf8A5hhi?XP&+lby zkYcFwF;C^h+=@31`>ufcFB+_a0}p(`yn>~L2l&2ej{k>lYdP`1@xA{@@cT@gT1mkD z96mNr@_WdJ!hnC$ChKt+hHnAC01r_1VqSAjo{;tEb$#Gb*F}2(ae&|NfzyBvP(H(f ze`u!fcrL0}xqYkkiVH_v(Ba1t2eb+IbMgVgwZWnT+VPV4koJ$3`7nHd+!+US0QXTk zfMf6q52*ev+>g+s9A8-8PFMK-1^Q~d`@mLpM#sKTONOnlEV_h!W2<%g*e~*AkQ)Dv z{Ux;JD?V7z-zr`>LXXH6%twPaI=HPI{ZMjSWM{LU&*1^Z8{{3%^Lf2Uoxjtb^a6N? z##!gmpI3Zf$^YZGY6#@R~og9lqpk z2L}98Ca5{^FMmfIeJ88e`{-%vX84giu~UGCFUhps63;(Rk z5%!D|7rcFyG+ced$6&zHHov zSIFy6Cf|3_%ZH5Tj&Od04s^%(&1c{l9bL?pEl>aAtle#amwl~c>gW@KPJ79o-({Q( zbbTxR(bQ6pBX2+V@B#DLr(w_S)f%gVuxGwtc>1uHd_kN~-5-m*iwDUqbnqm2L|fL zPRIv9Uy}%5aa|i8ynEJ(7XBG;nQSjQt#`WHZ8I>ye;XnO|2Nq0Xg$A?d@=*i3HT@f z7tsM9b~~L7>V)7P9fuBpE;ghC_@2VE|H~TJC0pT{*O9HMhbO2vINoIr0qX`|&uiZ*CVl@yzSfbvVtBJK_)V`xD#;c2j-Q3G;6}8~KFank~|) zE9QFmU7vYl;mhzl5|7rKyz_tSVc$j^I@`{`Iga0~Vf~Irv~X z=K5uyZ5d0y9_u;@@BJnG?T9PljD0SMGng*|{=?@h3`BTp2RLf|&YdX}4U_%AIv*u| z-|WL*wE#}wg@z$tPQ9is+?82Yx9DWzznY^zkL#)lpWJ5?$=lKYP!BiFXJZ>BD0e9CT}hG(DkEdFtr{LJx16+u?wh zPa^p}+Cz-F7Y5{6*8*`qZK{O_=gm3u#2)*{;s@~mm^8;gJWw#ZRQPYt*tdRYhrm(! zfWawgf&|NY49six$^%q7K>MeH_x)4#>0!&;r_*MAwME4)tfCj+-SLA=oggUwOGCXhxTsQoYyYoGPw?_{!o?iCgf83+ccQ?={ zQ)UVEw`*G84*a7Z(Ah~}c@Kv!z+aI+3w@p=xc7IZJHYqdli|T0w$JT%<}mct&!6x* zX^ByCoE+jGvgFttccOFgp*d#R<+*tj?sq!W*T8?9{3ibY{?n)ERd5e&-46Ht-8NyP zyC2X6qq*$%WYN|4Bk!64ozYu-vle!UJot~tR^yI!JE55ZeL|h#AWP`$nUB&HaL;-@ z*@~0j2hYMkW#U>qOb(>$?E!UiejGZSe#g31zO377Y5ddw(~kH-$A9uK%xAd$75iE8 z8QUk++-Lc3#Ot!ee_OIV?;`ok{ziSv;)`mXAoyd0M54dxZvOLw<$4bOrvxSmJoDU| z1J@h3BXRr5$$W0``^=xCP8JyVJ@61&x3k|5;DPPSW)A;e%&3c!zobopKIMFu`*0#$ zI9D9y_({!QwB*mlFJ9}Dj;KfIctqX>%kyJBpm2kH0cT;*0py#|1?-zJ&527`=8yHq z`~iO7JL(4AXVk^WD3W1XR(7jD3andX%4SpeE@^c4e+Y!Bqh){xR_0d(26Zk-R05e=F@k^Y8{$?Je>KYUB9e#f~0awHYlp7}S-~Mz8p5TEtf5`PS z?}7N7>z$BJe9i;2(}a8gv|`|aOTtyGa7~;Bu2F|3U?=d3j`jDIEsGlOyVPvbw|Xgl zFr&UJ{V@N2HWB_k!x~n(fJ~vj=&>{ACHM>4?@sn)u=-M9z7ke$kcWtE#V7E|?6 z{HIy{#fT129AU_>Wy%NNjl2$Tptl*vFX!lO9zXiq-PJmD4qPsu(a*rK=975zJkP5a zu_wxRfP4G`NAYg0Rx7~$VLfC2TJRreu$BKu<`|CdYkrsVe=Q!g@&NxGeMw&MF5%xI zfOpI?z%zP#0Qao-xGvmdyAeO{#C0sqOo7UF4-{)ayg^8?mp>^S;l@aNRYEXz6bcZ_%Cj|_f~IQ<}^e-UQu34JjO zh{1m0KO^rn^Iy2nn(vMZ+4r?hFIITA@I;41miZ#wRKI7W?%%>8Io1Cgjhg%|I$)NA zH|A}!u3tj`ZKEOF_lGB(A9$b4mJ!^a@_m8%N5R_Q<=^>*aId;$&U(bIH(SWoQsQ;+ zFzdFSN*~u>dg+V<_dj62LRp5rQ) zY8m-MDikZ|1IPU_q+WEN$S0Pbb;`E3827~YwR$~u`1@svo__2e8&{sVKlsOtqts#* zKiNOH$I7?4KiDDVjfsT^*3knAc~0JE4D+8gjIf8&PZ9i23WOo?57SX65gtHiqlc&$ z@*mv~`Ax_5cf=?33;c=1Oinx?yvq_7s&5EB@LsmL_8>J^v3x*epBLiSrCwiSd|=rp zE){=#&_y_y=kpPy2QEa9{{qLjt0V9Zu8W#%i4K*;>lopFMnjNrKbds9vBLlJ<2j8J ziYqD}BJSuC7(98s^B#(v9uP|vbPPM;t9#IG9q^sn*Y40S$+*D!o# zwvmAc;j8#1X|-DXC$cuh){40k|7nxFQ2Za}&e}KR=xJ z;sNS)&v}3kOwj$fouvWyT5nzczd2uy%%ga}>L=w`{mP!dUowA4ejjh=1Gw+qCXY=# z;bQ!Aq+chF&$k1(*Lpx6+oBLJ{6mNJ@D<+iyz^l{6aFc~ za=u==eOCGX5q-AI?*VUfPhEnq&vLFu&)>pp@MDGh1;RH0_oPLdjQ<<(p%MOR3+C8f z?eIQFzKnEQ279m`X}>1i91PK`=!t56D!kZTmNkd}Ri4@%$3y&5FW{Gjvq)d^8UrAq zlGtTpw#U+D2!rm-EP=Z_ie%aqz!Lp7a&x>R3gLvTsG;2ir(dSs9*^@&=F*t_sJY~~;OkRMAAh~xXe9Lc)YY$A;h(q){F5&pfcLMO zj8oxWx6d5Kf8wyHEcnCzk1S1jJ-Ner>@$lFplmR-;2zk{0ybk{f&U|8W^2jAa70~b zoKiG4%`nAT48AJk%p&!mE1d>wLAGea~l>hId!g9tF`WK`&;;}H~p>f1^uYr ziwEQ{v8(7b@J}86bZp3%mwcWHj=medrf!Khm|t+3DBt%M{WIkIkO8dg<5`bq zM&91<4hb{@`ky|7iLOUHv!74^w@ zT&v5v0p&X{+-D8^33fbY9D{4z&reetMdi-+l0i=8Za!F>-<3#f#R2Mz$(x)W+(+;gZf?b|NAs(PTXoLH7 zgFez3*L_udZ<${Joq==viRvG+^ktRsCHt{*j9o4}-JJKa{=rmyK>JapADZ38M90*d zUPc$t?@AyKFYtNrLi~VuG39mQ3HXY*Vfn0I!@>hF7Oyj3xQN6ROJFJd*T7GZ?znbs z1pYsrF}}e6rwsd!{nUI4?$4P|!<7y{o<4r)ASU$nW(agcoqs+bR`?%GnKuspaa)-z zb=uS%q4((%GvJ*(82Hcc81`pNIOedHA0S7pTvC?H-aREM)12OXJt!>%lNHvVaz)|1g`+nr^*?TY1aOdv%+k^vu z6c2#=RCZ3-mD3M1osSv`Isx6WV(5!Ixi(l0=m^q>-y`gV_JRv-TRk}*Npm;;LZ?oRG?DV_Ny7-_uoiBCV zXZ>sJC)`H8pMBBLG4vat>s&&;;XwhfV2_G~{h^Z&!Ff%;hyJU&1mmM7oiQospb_pN z3`hSA_bbh*qgQz<`s+^S{grq<|5W`Wk@-5LeaDvhI_ShKm;bN9995q$`V{|979u$$>yWQy+ z>h?2qCUys1-Z$j+D9f09SL^XN8jOF{@8N$7?nk8u|MJ)J4-EG#Vchh&5b&>ZU)__3 z?(=%Uk0A@vDBLSPco6vOvP{5#h1Unk1M)33_rTxthl=g9WFTsU}O34d6Q zoB#IR?Hr4~tLOL3y)lhN5&Zyv)E#+p>JNSC4d{F$XFNys8G!!+{YK_?WLe+pec>K|;&Pq`s{XlM zNuNGH?v+?@KWTG+_JP4E_h;YviRy%hAx`LnqTdl74e*kuV9LLTq)W%1=goYmv1ffW z{@w@rhUo7<@8JRT#LQo{=zyXJA6%vEw!wY6+Fd^V4j#SAW3A>JkndDI;VNY#^&8)q z^8odGc*5!Ziuc4LU3fs2zW9axM0UC=%XFNICxBhWzwg4|#HXK<=s%74dmiJ4{Qa!m zUYEa*tREGacSHU&HO$9R|AOBR`1$3?U$@r@15WMr{5tVXG^~SxJ|D)f63vS*LHqUy z;F%(RRVa@S4`@BY1RX%%&z@|twP|v2e=<08#$rnZLk`}-@5CS3D*qSp{~1Cqq3;$s z-LMbMeQ=PuhxtR^Pkzq92h^RwH8j2y{u$mg;otKk%jxvZ@59%~+!^@4-y=^I$>;Gk zv#5L?LWd*p-z#L%3*_};pZ#Bo*!O+v#ieY;=UA56eJR2Nxa|ye06$D-hWb6z@M~s2 z*&Q=)wetJ)@5rAK&PvDblef6WxZP*CvVN8){%8pQ$MU-l-KmW?6sOKe=mqlEv{U^s z630Hcon;2x4_4D!{=huY`z?NdrIx1tfqlR*FZ}~|&v{=9Sn9|Vf6v{UjmnATcsgaB zKP>NS=j z-7XCdt|R=XJhTb24hhee!aM8Sty=gsB`~n?tziKi`FF=c;`z`Fz2*mDiT|cL zhMtz#!GuNz;a+}FmURr4;C~<5;=JM^GIiB+KBQ~r;nDwJH#h|U%#(iJM_BQ|gn)k6 zQzs;lhX;m@8o0Dqm4~z7x`yKuKa!U$b)V2({mSCcWaq@|y5IC+a$n(%8TS$X(Bp?k zK3VTDLkZiYpeze5B)ND3fuCSU=t^*eX89@-gK7;}N%Y)zXKz#|8kh+W*Ka?kDaO{_#6K z;a_=@_&?1fV0T;iFYsfL--A=>fcxNm62QHOpd)cT!}_V`n6M4M?(f3;;NSNNTe|_g z*Bhg!*qvUncu*bJYBk}XpSi*R_c>AUze8D}Z}4A=uhFx+EW^3#I4&tvGH&;;4g1EU zXB*%*;vcZ7{1tjv_-SK>#y@fQ@QX$M#68fuKjujeUr?u^{#DhB!2^%|6n{DFFn~19 z73U_5^8(^dhU$lhAJxZ;`^WAxeUUx@Po8o#ukb&asD8K0_L=l6yBqWu)fm^RgUfOJFXG-Y6rwK{^&3>^`F}#K9vt=DEeD+)=mB{rWL@AP+E@&fgX8i)gLx-$?mo zaIg8+vc#A5_$WMDVS}sid-IsO!G!zzD1CsQdqn;Y$8X?Q=;;(5P`p0L!8`0m{m>4~ z3j1YX-`ZeZEap3~4#C901KfV*Kf!?dffu1Q^gdUN+zQk~%W~e7oy@w;h^2w85x~Em zWB3dHBj?9(Q{52$WL`2n(LQBg0C)htB#)wfP}^+>o}p1MyViYHpd0^EM--ouALqWw z&v2h_Alp}D3v_?vb<%n(W)D3900KO+zQJ?9;pjUo9K7&};!%!?8DKa2cpTlSw+AGi73U&kG^Jh(sAJS*_c<67p-YtedGj@MUc@Nv)a&+GPj z1`rZ-0LzBd4gLK*$9__nqS0Xf29|iPpVRjb6>edO)>KKKXyzL!i)(?~K!5CNnWpA(IA$D| zjML*R<36BI#{kk}aYs|}0RFWT9-9Z~vbKGafPdEa0LRetdcblU%lN=QpuZ0r9!y{S zfieGAcrb_P!xR5Nve080{yxW6{{M-2vfkM^HBbEGjuO)3!%lIu(jSy}B0MnAI7^62 z&|%WIhv)!GCi;*XNLrp6YWJ3N-Y2#pVijpRzi2QNN!y$^*-N8))Ab@_#H0R>l7-EI<(+ zx#L>D|GoQtc%=UL@O$@r(|fS=zmJc)Bzn!`@CZLcUVwUDdjjswaFZ8uIvhCf1$aLo z9$h}S#V7~=E)WO7f55#PkEQpC1Hr%6Pt3{Jpwr3!ldmm{YJD`^3iTW25AqYy|NX39 z<9mrmCX?`p`I4Q^La)Qeiu1Uup76+wr=t!-4BD#4g+`Pn%bGXTCVPU-5ip{plZba4-Fx zF!O(eHO%*8e(*eDeh>Y9qZ~fKEv#7je((nkN4<}9Lt()`@x;<$%Vn$S=m&I;dGpMp z?-dXsglO;9qfy1OK?|I(XR!r$@5lJNW^4a-{K5QYb~AK31Mk_$SXg zc3AoAfK_~8o`lD%KO`UaBzdCvz=Gp3MCQRixLWZ~;)6DNBVy^xti>lr%NUvXBs^hx z0`bHX(wj5)B!7b18Ry_%JfqK*-{*TCgM0D7km1|0!~vu=+QL8gi@qPfAIFSiEYk?q zgiG4Yh-2Mu_5IigUuwLf6aOF2cr7?@jaT9$@E>kaU!g8e-+=lm!F#98{e%zXR)v4o zXCp5Xz*>A8s)*l&DYzX!KI=l7gnmH$JI*Anng-yCrh>mpF5jC>y-*VXrux0r(eW8x6_ z1U=R6k|*@6o78GK^%LPxJ=aZ~-_w1itwsHWyVL`{S6sz@XWUQLO~Y^OQ8z+)B&=6L zxZF?Zk7^LF8rCtHQ#s;#SQl~m%)kTH{M6leqs(e6j_0r>-;={1^xYJO=adl-8uHaV z&r`RP>_i5H*t$@=z_U@m^{Sa9C+dY^ub*DfBeNz{+{|!N!&n6N<4JYwPw~GMFDyCsJK)9#`gK?8FEP)6cxeVMcFc`pv1}RD_fxK) z`o8?JRlZ;OG@c*Z9@GQl7g^T-3;tP0%d@UFby4!w&;z(P==?0j|2_uqs~$WOHm^kV zxz_Va{v|#}-kb=x(xbw$?gRLLVjTMGU&52F@=5;^pL7@S0JfUfP~o2we-hw{tgpYr zy`uxn6W;X}_(yNdW_-_($Gq-|d>4yZ8~iiw$KV5h#yE)Jup_=i?|0rDk6ne+{z&7r zR-pH}&I$XZ+FkAd{Z8P2w?SYK7_X#F&;fWN3JJnLwA~E+{h~`>xbW}zLI0kRpWyTM z+19n;_&({xK)k^|(W}A<^B<(2k6Y(A;ZJwW{Ty3x&wU(O{6BK1<{st8$dkhU-W@BS zFFqhWZu}#MO^+OUTx)s~=Ma2WP^yTB6 z`_iI*mB16IC8PI21O7Qb81)MdIB%Y7PG6F2Jnn34oE7frdn7N&H`a3o$;V} zO8j=nT_^$;nYk1XKu_>1c@^?z-1ps+`DhWbe0&+`hax09^PlZoIe)Klnee=(0F7Y n;hV-7aDAP}zDS literal 0 HcmV?d00001 diff --git a/Templates/Full/game/art/environment/FogMod_light.dds b/Templates/Full/game/art/environment/FogMod_light.dds new file mode 100644 index 0000000000000000000000000000000000000000..d726795fa1de24d57dc54259c7c57f984e3d6704 GIT binary patch literal 131200 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%0DDkgQ;%|w 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൏u$o87pO_E*IzlWcQhKlOkZMNW<&fqwN}9Y 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 bRO-0LHU0XoSI+pJx4tj`BkL3><($#K7P0$f literal 0 HcmV?d00001 diff --git a/Templates/Full/game/art/environment/Fog_Cube.DAE b/Templates/Full/game/art/environment/Fog_Cube.DAE new file mode 100644 index 000000000..34cad9f48 --- /dev/null +++ b/Templates/Full/game/art/environment/Fog_Cube.DAE @@ -0,0 +1,177 @@ + + + + + Richard + OpenCOLLADA for 3ds Max; Version: 1.4.1; Revision: exported; Platform: x64; Configuration: Release_Max2011_static + file:///G:/Documents%20and%20Settings/Richard/Mijn%20documenten/3dsmax/scenes/FogVolumes.max + + 2014-08-16T10:10:23 + 2014-08-16T10:10:23 + + Z_UP + + + + + + + + 0 0 0 1 + + + 0.588 0.588 0.588 1 + + + 0.588 0.588 0.588 1 + + + 0.9 0.9 0.9 1 + + + 0 + + + 0 0 0 1 + + + 1 1 1 1 + + + 1 + + + + + + + + 0 + 0 + 0 + 1.5 + 0 + 3 + 1 + 0 + + + 1 + 1 + 0 + 0.1 + 0 + + + + + + + + + + + + + + + -0.85 -1 -0.85 0.85 -0.85 -1 -1 0.85 -0.85 0.85 0.85 -1 -0.85 -1 0.85 1 -0.85 0.85 -1 0.85 0.85 0.85 1 0.85 -1 -0.85 -0.85 -0.85 -0.85 -1 1 -0.85 -0.85 0.85 -1 -0.85 -0.85 1 -0.85 -0.85 0.85 -1 0.85 1 -0.85 1 0.85 -0.85 -0.85 -0.85 1 -1 -0.85 0.85 0.85 -0.85 1 0.85 -1 0.85 -0.85 0.85 1 -0.85 1 0.85 0.85 0.85 1 1 0.85 0.85 + + + + + + + + + + -0.341586 -0.341586 -0.8755786 -0.341586 0.341586 -0.8755788 0.341586 0.341586 -0.8755788 0.341586 -0.341586 -0.8755788 -0.341586 -0.341586 0.8755786 0.341586 -0.341586 0.8755788 0.341586 0.341586 0.8755788 -0.341586 0.341586 0.8755788 -0.341586 -0.8755786 -0.341586 0.341586 -0.8755788 -0.341586 0.341586 -0.8755786 0.341586 -0.341586 -0.8755788 0.341586 0.8755786 -0.341586 -0.341586 0.8755788 0.341586 -0.341586 0.8755786 0.341586 0.341586 0.8755788 -0.341586 0.341586 0.341586 0.8755786 -0.341586 -0.341586 0.8755788 -0.341586 -0.341586 0.8755786 0.341586 0.341586 0.8755788 0.341586 -0.8755786 0.341586 -0.341586 -0.8755788 -0.341586 -0.341586 -0.8755786 -0.341586 0.341586 -0.8755788 0.341586 0.341586 + + + + + + + + + + 0 0 0 1 0 0 0 1 0 1 1 0 0 0 0 1 0 0 0 1 0 1 1 0 0 0 0 1 0 0 0 1 0 1 1 0 0.07542458 0.07542461 4.99755e-4 0.07542479 0.07542461 4.99547e-4 0.07542455 0.07542461 4.99755e-4 0.07542461 0.9245752 4.99547e-4 0.07542458 0.9245754 4.99755e-4 0.07542458 0.9245754 0.9995003 0.07542455 0.9245754 4.99755e-4 0.07542455 0.9245754 0.9995003 0.9245752 0.07542461 4.99576e-4 0.9245754 0.07542479 4.99547e-4 0.07542458 0.07542461 0.9995003 0.9245752 0.07542461 4.99576e-4 0.9245752 0.07542461 0.9995004 0.9245752 0.9245754 4.99547e-4 0.07542455 0.07542461 0.9995003 0.9245752 0.07542461 0.9995004 0.07542461 0.07542479 0.9995005 0.9245752 0.9245754 4.99576e-4 0.9245752 0.07542461 0.9995005 0.9245752 0.9245754 4.99576e-4 0.07542479 0.9245754 0.9995005 0.9245752 0.9245754 0.9995004 0.9245754 0.9245752 0.9995005 0.9245752 0.9245754 0.9995004 0.9995003 0.07542461 0.07542458 0.9245752 4.99547e-4 0.07542461 0.9245752 4.99547e-4 0.07542461 0.9995003 0.07542461 0.07542458 0.9995003 0.07542461 0.9245754 0.9245752 4.99547e-4 0.9245754 0.9245752 4.99547e-4 0.9245754 0.9995003 0.07542461 0.9245754 0.9995003 0.9245754 0.07542458 0.9245752 0.9995005 0.07542461 0.9995003 0.9245754 0.07542458 0.9245752 0.9995005 0.07542461 0.9995003 0.9245754 0.9245754 0.9245752 0.9995005 0.9245754 0.9995003 0.9245754 0.9245754 0.9245752 0.9995005 0.9245754 0.9995004 0.07542482 0.07542461 0.9995003 0.9245754 0.07542461 0.9245752 0.9995004 0.07542461 0.07542455 0.9995003 0.07542461 4.99606e-4 0.9245752 0.07542461 4.99725e-4 0.07542458 0.07542461 0.07542479 4.99576e-4 0.07542461 0.9245754 4.99755e-4 0.07542461 0.07542458 4.99755e-4 0.9245754 0.9245752 4.99576e-4 0.9245754 0.9995003 0.07542458 0.9245754 0.9995004 0.9245752 0.9245754 0.9245754 0.9995003 0.9245754 0.07542482 0.9995004 0.9245754 4.99755e-4 0.9245754 0.9245754 4.99576e-4 0.07542482 0.9245754 0.9995003 0.07542461 0.07542458 0.9995003 0.9245754 0.07542458 0.9995003 0.9245754 0.07542458 0.9995003 0.07542461 0.07542458 0.9995003 0.07542461 0.9245754 0.9995003 0.9245754 0.9245754 0.9995003 0.07542461 0.9245754 0.9995003 0.9245754 0.9245754 + + + + + + + + + + -0.8644259 0.01841655 0.3300502 -0.8715108 -0.05526615 0.3184382 -0.8644259 0.01841664 -0.3300501 -0.8715108 -0.05526611 -0.3184382 0.8738725 -0.06754867 0.3145678 0.8597026 -0.006149054 -0.3377912 0.8738725 -0.06754874 -0.3145678 0.8597026 -0.006148929 0.3377911 0.883319 -0.2990854 -0.116681 0.8478944 0.3571441 -0.06756432 0.8597026 0.3377913 0.00614921 0.883319 -0.2990854 0.116681 0.2990854 0.883319 -0.116681 -0.3571441 0.8478944 -0.06756432 -0.3377913 0.8597026 0.00614921 0.2990854 0.883319 0.116681 -0.883319 0.2990854 -0.116681 -0.8478944 -0.3571441 -0.06756432 -0.8597026 -0.3377913 0.00614921 -0.883319 0.2990854 0.116681 -0.2990854 -0.883319 -0.116681 0.3571441 -0.8478944 -0.06756432 0.3377913 -0.8597026 0.00614921 -0.2990854 -0.883319 0.116681 0.8360862 -0.3764972 0.1289794 0.7071068 -0.7071068 0 0.7071068 0.7071068 0 0.3764972 0.8360862 0.1289794 -0.3764972 -0.8360862 0.1289794 -0.7071068 -0.7071068 0 -0.7071068 0.7071068 0 -0.8360862 0.3764972 0.1289794 0.8360862 -0.3764971 -0.1289794 0.7071068 -0.7071068 0 0.3764971 0.8360862 -0.1289794 0.7071068 0.7071068 0 -0.3764971 -0.8360862 -0.1289794 -0.7071068 -0.7071068 0 -0.8360862 0.3764971 -0.1289794 -0.7071068 0.7071068 0 -0.376497 0.1289792 0.8360862 -0.3764973 -0.1289798 0.8360861 -0.8833191 -0.2990855 0.1166808 -0.883319 0.2990853 -0.1166812 -0.3764971 0.1289794 -0.8360862 -0.3764972 -0.1289795 -0.8360862 -0.8833191 -0.2990855 -0.1166807 -0.883319 0.2990853 0.1166812 0.883319 -0.2990853 0.1166812 0.8833191 0.2990855 -0.1166807 0.3764971 0.1289797 -0.8360862 0.3764971 -0.1289793 -0.8360862 0.883319 -0.2990853 -0.1166811 0.8833191 0.2990855 0.1166808 0.3764972 0.1289799 0.8360861 0.3764971 -0.128979 0.8360862 0.3764972 0.8360862 0.1289794 0.3764971 0.8360862 -0.1289794 0.8360862 -0.3764971 -0.1289794 0.8360862 -0.3764972 0.1289794 -0.8360862 0.3764972 0.1289794 -0.8360862 0.3764971 -0.1289794 -0.3764972 -0.8360862 0.1289794 -0.3764971 -0.8360862 -0.1289794 + + + + + + + + + + 0.1043954 -0.9396398 0.3258505 -0.06496345 -0.9379679 -0.3405817 0.1043953 -0.9396398 -0.3258505 -0.06496349 -0.937968 0.3405817 0.05187585 -0.9370471 -0.3453283 -0.1307439 -0.939827 -0.3156443 0.05187577 -0.9370471 0.3453283 -0.1307438 -0.939827 0.3156443 0 0.3634471 -0.9316148 -0.196368 0.2889368 -0.9369926 0.1307441 -0.3156442 -0.939827 0 -0.3634471 -0.9316148 -0.3634471 0 -0.9316148 -0.2889368 -0.196368 -0.9369926 0.3156442 0.1307441 -0.939827 0.3634471 0 -0.9316148 0 -0.3634471 -0.9316148 0.196368 -0.2889368 -0.9369926 -0.1307441 0.3156442 -0.939827 0 0.3634471 -0.9316148 0.3634471 0 -0.9316148 0.2889368 0.196368 -0.9369926 -0.3156442 -0.1307441 -0.939827 -0.3634471 0 -0.9316148 0.2608475 0.2608475 -0.9294714 0.6191276 0.6191276 -0.4830755 -0.6191276 0.6191276 -0.4830755 -0.2608475 0.2608475 -0.9294714 0.2608475 -0.2608475 -0.9294714 0.6191276 -0.6191276 -0.4830755 -0.6191276 -0.6191276 -0.4830755 -0.2608475 -0.2608475 -0.9294714 -0.2608475 -0.2608475 -0.9294714 -0.6191276 -0.6191276 -0.4830755 0.2608475 -0.2608475 -0.9294714 0.6191276 -0.6191276 -0.4830755 -0.2608475 0.2608475 -0.9294714 -0.6191276 0.6191276 -0.4830755 0.2608475 0.2608475 -0.9294714 0.6191276 0.6191276 -0.4830755 0.2608476 -0.9294715 0.2608473 -0.2608474 -0.9294714 -0.2608479 1.81809e-7 -0.363447 -0.9316149 2.27262e-7 -0.3634472 -0.9316148 0.2608475 -0.9294714 -0.2608475 -0.2608475 -0.9294714 0.2608477 2.72714e-7 -0.363447 0.9316149 2.72714e-7 -0.3634472 0.9316148 2.72714e-7 -0.3634472 -0.9316148 2.72714e-7 -0.363447 -0.9316149 -0.2608474 -0.9294714 -0.2608478 0.2608476 -0.9294714 0.2608474 1.81809e-7 -0.3634472 0.9316148 2.27262e-7 -0.3634471 0.9316149 -0.2608473 -0.9294714 0.260848 0.2608477 -0.9294715 -0.2608472 -0.2608475 0.2608475 -0.9294714 0.2608475 -0.2608475 -0.9294714 -0.2608475 -0.2608475 -0.9294714 0.2608475 0.2608475 -0.9294714 -0.2608475 -0.2608475 -0.9294714 0.2608475 0.2608475 -0.9294714 0.2608475 -0.2608475 -0.9294714 -0.2608475 0.2608475 -0.9294714 + + + + + + + + + + + + + + + + + +

9 0 21 0 13 1 25 1 3 2 15 2 3 2 15 2 1 3 13 3 9 0 21 0 16 4 28 4 18 5 30 5 22 6 34 6 22 6 34 6 20 7 32 7 16 4 28 4 0 8 12 8 11 9 23 9 19 10 31 10 19 10 31 10 4 11 16 11 0 8 12 8 10 12 22 12 15 13 27 13 23 14 35 14 23 14 35 14 5 15 17 15 10 12 22 12 14 16 26 16 12 17 24 17 21 18 33 18 21 18 33 18 7 19 19 19 14 16 26 16 2 20 14 20 8 21 20 21 17 22 29 22 17 22 29 22 6 23 18 23 2 20 14 20 0 8 36 24 8 21 20 21 9 0 37 25 1 3 38 26 10 12 39 27 11 9 23 9 2 20 40 28 12 17 24 17 13 1 41 29 3 2 42 30 14 16 43 31 15 13 27 13 4 11 44 32 16 4 45 33 17 22 29 22 5 15 46 34 18 5 47 35 19 10 31 10 6 23 48 36 20 7 49 37 21 18 33 18 7 19 50 38 22 6 51 39 23 14 35 14 9 0 21 0 8 21 52 40 2 20 53 41 2 20 53 41 13 1 25 1 9 0 21 0 13 1 25 1 12 17 54 42 14 16 55 43 14 16 55 43 3 2 15 2 13 1 25 1 3 2 15 2 15 13 56 44 10 12 57 45 10 12 57 45 1 3 13 3 3 2 15 2 1 3 13 3 11 9 58 46 0 8 59 47 0 8 59 47 9 0 21 0 1 3 13 3 16 4 28 4 4 11 60 48 19 10 61 49 19 10 61 49 18 5 30 5 16 4 28 4 18 5 30 5 5 15 62 50 23 14 63 51 23 14 63 51 22 6 34 6 18 5 30 5 22 6 34 6 7 19 64 52 21 18 65 53 21 18 65 53 20 7 32 7 22 6 34 6 20 7 32 7 6 23 66 54 17 22 67 55 17 22 67 55 16 4 28 4 20 7 32 7 11 9 23 9 10 12 68 56 5 15 69 57 5 15 69 57 19 10 31 10 11 9 23 9 4 11 70 58 17 22 29 22 8 21 20 21 8 21 20 21 0 8 71 59 4 11 70 58 15 13 27 13 14 16 72 60 7 19 73 61 7 19 73 61 23 14 35 14 15 13 27 13 12 17 24 17 2 20 74 62 6 23 75 63 6 23 75 63 21 18 33 18 12 17 24 17

+
+
+
+
+ + + + + 0 0 0 + + + + + + + + + + + + + + + + + + + + 1 + 1 + 1 + 1 + + + + + + + + +
\ No newline at end of file diff --git a/Templates/Full/game/art/environment/Fog_Cube.cs b/Templates/Full/game/art/environment/Fog_Cube.cs new file mode 100644 index 000000000..3c686032c --- /dev/null +++ b/Templates/Full/game/art/environment/Fog_Cube.cs @@ -0,0 +1,8 @@ + +singleton TSShapeConstructor(Fog_CubeDAE) +{ + baseShape = "./Fog_Cube.DAE"; + lodType = "TrailingNumber"; + neverImport = "env*"; + loadLights = "0"; +}; diff --git a/Templates/Full/game/art/environment/LightVolume_Sphere.DAE b/Templates/Full/game/art/environment/LightVolume_Sphere.DAE new file mode 100644 index 000000000..81c93d8e0 --- /dev/null +++ b/Templates/Full/game/art/environment/LightVolume_Sphere.DAE @@ -0,0 +1,423 @@ + + + + + Richard + OpenCOLLADA for 3ds Max; Version: 1.4.1; Revision: exported; Platform: x64; Configuration: Release_Max2011_static + file:///G:/Documents%20and%20Settings/Richard/Mijn%20documenten/3dsmax/scenes/lightfog.max + + 2014-08-22T22:51:35 + 2014-08-22T22:51:35 + + Z_UP + + + + + + + + 0 0 0 1 + + + 0.588 0.588 0.588 1 + + + 0.588 0.588 0.588 1 + + + 0.9 0.9 0.9 1 + + + 0 + + + 0 0 0 1 + + + 1 1 1 1 + + + 1 + + + + + + + + 0 + 0 + 0 + 1.5 + 0 + 3 + 1 + 0 + + + 1 + 1 + 0 + 0.1 + 0 + + + + + + + + + + + + + + + 0 0 5 -9.48283e-8 2.169419 4.504844 -0.9412758 1.954579 4.504844 -1.69612 1.35261 4.504844 -2.115027 0.4827411 4.504844 -2.115027 -0.482741 4.504844 -1.69612 -1.35261 4.504844 -0.9412759 -1.954579 4.504844 2.58701e-8 -2.169419 4.504844 0.9412759 -1.954579 4.504844 1.696121 -1.35261 4.504844 2.115027 -0.4827399 4.504844 2.115026 0.4827427 4.504844 1.696119 1.352612 4.504844 0.9412734 1.95458 4.504844 -1.70875e-7 3.909158 3.117449 -1.69612 3.522029 3.117449 -3.056303 2.43732 3.117449 -3.811147 0.8698694 3.117449 -3.811147 -0.8698691 3.117449 -3.056303 -2.43732 3.117449 -1.696121 -3.522029 3.117449 4.66162e-8 -3.909158 3.117449 1.696121 -3.522029 3.117449 3.056304 -2.437319 3.117449 3.811147 -0.8698672 3.117449 3.811146 0.8698722 3.117449 3.0563 2.437323 3.117449 1.696116 3.522031 3.117449 -2.13077e-7 4.87464 1.112604 -2.115027 4.391898 1.112604 -3.811147 3.039288 1.112604 -4.752422 1.084709 1.112604 -4.752422 -1.084709 1.112604 -3.811147 -3.039288 1.112604 -2.115028 -4.391898 1.112604 5.81295e-8 -4.87464 1.112604 2.115028 -4.391898 1.112604 3.811148 -3.039287 1.112604 4.752423 -1.084707 1.112604 4.752421 1.084713 1.112604 3.811144 3.039291 1.112604 2.115022 4.391901 1.112604 -2.13077e-7 4.87464 -1.112605 -2.115027 4.391898 -1.112605 -3.811147 3.039288 -1.112605 -4.752422 1.084709 -1.112605 -4.752422 -1.084709 -1.112605 -3.811147 -3.039288 -1.112605 -2.115028 -4.391898 -1.112605 5.81295e-8 -4.87464 -1.112605 2.115028 -4.391898 -1.112605 3.811148 -3.039287 -1.112605 4.752423 -1.084707 -1.112605 4.752421 1.084713 -1.112605 3.811144 3.039291 -1.112605 2.115022 4.391901 -1.112605 -1.70875e-7 3.909157 -3.11745 -1.69612 3.522028 -3.11745 -3.056302 2.437319 -3.11745 -3.811146 0.8698692 -3.11745 -3.811146 -0.869869 -3.11745 -3.056302 -2.437319 -3.11745 -1.69612 -3.522028 -3.11745 4.66162e-8 -3.909157 -3.11745 1.69612 -3.522028 -3.11745 3.056303 -2.437318 -3.11745 3.811146 -0.8698671 -3.11745 3.811145 0.8698721 -3.11745 3.0563 2.437322 -3.11745 1.696116 3.52203 -3.11745 -9.48283e-8 2.169418 -4.504845 -0.9412755 1.954578 -4.504845 -1.696119 1.35261 -4.504845 -2.115026 0.4827409 -4.504845 -2.115026 -0.4827408 -4.504845 -1.69612 -1.35261 -4.504845 -0.9412756 -1.954578 -4.504845 2.58701e-8 -2.169418 -4.504845 0.9412756 -1.954578 -4.504845 1.69612 -1.352609 -4.504845 2.115026 -0.4827397 -4.504845 2.115026 0.4827425 -4.504845 1.696118 1.352612 -4.504845 0.9412731 1.954579 -4.504845 0 0 -5 + + + + + + + + + + 3.34297e-8 3.12427e-8 1 -1.46401e-7 0.4589442 0.8884651 -0.1991285 0.4134944 0.888465 -0.358817 0.286147 0.8884651 -0.4474375 0.1021247 0.888465 -0.4474375 -0.1021246 0.8884651 -0.358817 -0.286147 0.888465 -0.1991284 -0.4134944 0.8884651 -4.96273e-9 -0.4589442 0.888465 0.1991285 -0.4134944 0.888465 0.3588171 -0.2861469 0.888465 0.4474375 -0.1021244 0.8884651 0.4474374 0.102125 0.888465 0.3588168 0.2861473 0.8884651 0.1991281 0.4134945 0.8884651 -2.38195e-7 0.7935484 0.6085071 -0.3443078 0.7149624 0.6085071 -0.6204212 0.4947692 0.6085072 -0.7736526 0.1765811 0.6085071 -0.7736526 -0.176581 0.608507 -0.6204212 -0.4947692 0.6085072 -0.3443078 -0.7149624 0.6085071 -1.26699e-8 -0.7935485 0.6085071 0.3443078 -0.7149624 0.6085072 0.6204213 -0.4947691 0.6085071 0.7736526 -0.1765807 0.6085072 0.7736524 0.1765818 0.6085072 0.6204207 0.4947699 0.608507 0.3443072 0.7149627 0.6085071 -2.56849e-7 0.9764001 0.2159697 -0.4236442 0.879706 0.2159697 -0.7633804 0.6087754 0.2159697 -0.9519197 0.2172694 0.2159697 -0.9519197 -0.2172693 0.2159698 -0.7633804 -0.6087753 0.2159698 -0.4236441 -0.8797061 0.2159698 2.56849e-9 -0.9764001 0.2159697 0.4236443 -0.879706 0.2159698 0.7633805 -0.6087751 0.2159698 0.9519198 -0.2172689 0.2159698 0.9519196 0.2172702 0.2159698 0.7633796 0.6087762 0.2159697 0.4236434 0.8797064 0.2159697 -2.56849e-7 0.9764 -0.2159699 -0.4236442 0.8797059 -0.2159699 -0.7633803 0.6087754 -0.2159699 -0.9519196 0.2172694 -0.2159698 -0.9519197 -0.2172693 -0.2159699 -0.7633804 -0.6087753 -0.2159699 -0.4236442 -0.879706 -0.2159699 2.31164e-8 -0.9764 -0.2159699 0.4236442 -0.8797059 -0.2159699 0.7633805 -0.6087751 -0.2159699 0.9519197 -0.2172689 -0.2159699 0.9519195 0.2172701 -0.2159699 0.7633796 0.6087762 -0.2159699 0.4236434 0.8797063 -0.2159699 -1.95117e-7 0.7935483 -0.6085073 -0.3443078 0.7149623 -0.6085073 -0.6204211 0.4947692 -0.6085073 -0.7736524 0.1765811 -0.6085073 -0.7736524 -0.1765811 -0.6085073 -0.6204211 -0.4947692 -0.6085073 -0.3443078 -0.7149622 -0.6085073 1.77379e-8 -0.7935483 -0.6085073 0.3443078 -0.7149622 -0.6085073 0.6204212 -0.494769 -0.6085073 0.7736525 -0.1765806 -0.6085073 0.7736523 0.1765817 -0.6085073 0.6204206 0.4947698 -0.6085073 0.3443071 0.7149625 -0.6085073 -1.16624e-7 0.4589441 -0.8884652 -0.1991284 0.4134943 -0.8884652 -0.358817 0.2861468 -0.8884652 -0.4474374 0.1021247 -0.8884652 -0.4474373 -0.1021246 -0.8884652 -0.3588169 -0.2861468 -0.8884652 -0.1991284 -0.4134943 -0.8884652 -9.92546e-9 -0.4589441 -0.8884651 0.1991285 -0.4134943 -0.8884651 0.358817 -0.2861468 -0.8884652 0.4474374 -0.1021244 -0.8884651 0.4474373 0.102125 -0.8884652 0.3588167 0.2861473 -0.8884651 0.1991281 0.4134944 -0.8884652 2.59315e-8 -2.62439e-8 -1 + + + + + + + + + + -0.03571416 1 0.999001 3.79455e-8 0.8571429 0.999001 0.0714286 0.8571428 0.9990011 0.1428572 0.8571428 0.9990011 0.2142857 0.8571428 0.9990011 0.2857142 0.8571428 0.9990011 0.3571428 0.8571428 0.9990011 0.4285714 0.8571428 0.9990011 0.4999999 0.8571429 0.999001 0.5714285 0.8571429 0.999001 0.6428571 0.8571429 0.999001 0.7142857 0.8571429 0.999001 0.7857143 0.8571429 0.999001 0.857143 0.8571429 0.999001 0.9285716 0.8571429 0.999001 1 0.7142857 0.9990011 0.0714286 0.7142857 0.9990011 0.1428572 0.7142857 0.9990011 0.2142857 0.7142857 0.9990011 0.2857142 0.7142857 0.9990011 0.3571428 0.7142857 0.9990011 0.4285714 0.7142857 0.9990011 0.5 0.7142857 0.9990011 0.5714285 0.7142857 0.999001 0.6428571 0.7142857 0.999001 0.7142857 0.7142857 0.9990009 0.7857143 0.7142857 0.999001 0.857143 0.7142857 0.999001 0.9285716 0.7142857 0.999001 0.9999999 0.5714285 0.999001 0.0714286 0.5714285 0.999001 0.1428572 0.5714285 0.9990011 0.2142857 0.5714285 0.9990011 0.2857142 0.5714285 0.9990011 0.3571428 0.5714285 0.9990011 0.4285714 0.5714285 0.9990011 0.5 0.5714285 0.999001 0.5714285 0.5714285 0.999001 0.6428571 0.5714285 0.999001 0.7142857 0.5714285 0.9990009 0.7857143 0.5714285 0.9990009 0.8571429 0.5714285 0.999001 0.9285716 0.5714285 0.999001 0.9999999 0.4285714 0.999001 0.0714286 0.4285714 0.999001 0.1428572 0.4285714 0.9990011 0.2142857 0.4285714 0.9990011 0.2857142 0.4285714 0.9990011 0.3571428 0.4285714 0.9990011 0.4285714 0.4285714 0.9990011 0.5 0.4285714 0.999001 0.5714285 0.4285714 0.999001 0.6428571 0.4285714 0.999001 0.7142857 0.4285714 0.999001 0.7857143 0.4285714 0.999001 0.8571429 0.4285714 0.999001 0.9285716 0.4285714 0.999001 1 0.2857142 0.999001 0.0714286 0.2857142 0.9990011 0.1428572 0.2857142 0.9990011 0.2142857 0.2857142 0.9990011 0.2857142 0.2857143 0.9990011 0.3571428 0.2857143 0.9990011 0.4285714 0.2857142 0.9990011 0.5 0.2857142 0.999001 0.5714285 0.2857142 0.999001 0.6428571 0.2857142 0.999001 0.7142857 0.2857142 0.999001 0.7857143 0.2857142 0.999001 0.857143 0.2857142 0.999001 0.9285716 0.2857142 0.999001 3.79455e-8 0.1428571 0.999001 0.0714286 0.1428572 0.9990011 0.1428572 0.1428571 0.999001 0.2142857 0.1428572 0.9990011 0.2857142 0.1428572 0.9990011 0.3571428 0.1428572 0.9990011 0.4285714 0.1428572 0.9990011 0.4999999 0.1428571 0.999001 0.5714285 0.1428571 0.999001 0.6428571 0.1428571 0.999001 0.7142857 0.1428571 0.999001 0.7857143 0.1428571 0.999001 0.857143 0.1428571 0.999001 0.9285716 0.1428571 0.999001 -0.03571416 0 0.999001 0.03571432 1 0.999001 0.1071429 1 0.999001 0.1785714 1 0.999001 0.25 1 0.999001 0.3214285 1 0.999001 0.3928571 1 0.999001 0.4642856 1 0.999001 0.5357142 1 0.999001 0.6071428 1 0.999001 0.6785715 1 0.999001 0.75 1 0.999001 0.8214287 1 0.999001 0.8928573 1 0.999001 -0.07142836 0.8571429 0.999001 0 0.7142857 0.9990011 -0.07142836 0.8571429 0.999001 0 0.7142857 0.9990011 0 0.7142857 0.9990011 -5.96046e-8 0.5714285 0.999001 0 0.7142857 0.9990011 -5.96046e-8 0.5714285 0.999001 -5.96046e-8 0.4285714 0.999001 -5.96046e-8 0.5714285 0.999001 -5.96046e-8 0.4285714 0.999001 0 0.2857142 0.999001 -5.96046e-8 0.4285714 0.999001 0 0.2857142 0.999001 0 0.2857142 0.999001 -0.07142836 0.2857142 0.999001 -0.07142836 0.1428571 0.999001 -0.07142836 0.2857142 0.999001 0 0.2857142 0.999001 0.03571432 0 0.999001 0.1071429 0 0.999001 0.1785714 0 0.999001 0.25 0 0.999001 0.3214285 0 0.999001 0.3928571 0 0.999001 0.4642856 0 0.999001 0.5357142 0 0.999001 0.6071428 0 0.999001 0.6785715 0 0.999001 0.75 0 0.999001 0.8214287 0 0.999001 0.8928573 0 0.999001 -0.07142836 0.1428571 0.999001 + + + + + + + + + + -0.9749277 -0.2225218 3.95437e-8 -0.9749279 -0.03513084 0.01814697 -0.8631371 -0.4546571 0.01814702 -0.7818314 -0.6234899 4.56159e-8 -0.5803916 -0.7841328 0.01814686 -0.4338837 -0.9009689 4.26533e-8 -0.1826924 -0.9583017 0.01814685 1.04443e-15 -1 3.12427e-8 0.2511913 -0.9426673 0.01814685 0.4338836 -0.900969 1.36441e-8 0.6353235 -0.740326 0.01814683 0.7818314 -0.6234899 -6.65688e-9 0.8936221 -0.3913545 0.01814665 0.9749277 -0.2225219 -2.56394e-8 0.9749278 0.03512998 0.01814669 0.9749279 0.222521 -3.95437e-8 0.8631371 0.4546568 0.01814684 0.7818313 0.62349 -4.56159e-8 0.5803913 0.7841329 0.01814687 0.4338832 0.9009691 -4.26533e-8 0.1826917 0.9583018 0.01814686 -7.40827e-7 1 -3.12427e-8 -0.2511921 0.942667 0.01814683 -0.4338844 0.9009686 -1.36441e-8 -0.6353242 0.7403254 0.01814688 -0.7818321 0.623489 6.65693e-9 -0.852976 0.4757568 -0.03024468 -0.974928 0.2225204 2.56394e-8 -0.9545922 0.2647484 0.09073415 -0.974928 -0.08239544 0.1074507 -0.8783796 -0.4230055 1.2098e-7 -0.6078575 -0.7622294 -2.40103e-8 -0.2169419 -0.9504844 3.08126e-9 0.2169418 -0.9504845 1.00124e-8 0.6078575 -0.7622294 -1.22219e-8 0.8783796 -0.4230055 2.27489e-8 0.9749279 -1.0315e-7 -1.14217e-7 0.8783796 0.4230055 2.7348e-8 0.6078573 0.7622295 2.9091e-8 0.2169413 0.9504846 1.53869e-8 -0.2169426 0.9504843 -1.74207e-8 -0.6078583 0.7622288 -1.32728e-8 -0.8783801 0.4230047 1.08194e-8 -0.974928 0.08239487 -0.1074508 -0.9545922 0.2647484 0.09073415 -0.974928 0.08239487 -0.1074508 -0.9749279 -0.08239547 0.1074508 -0.9749279 -0.01037927 0.04692359 -0.8783797 -0.4230054 1.38382e-9 -0.974928 -0.08239544 0.1074507 -0.6078575 -0.7622294 -2.5756e-9 -0.2169418 -0.9504845 -4.34778e-9 0.2169417 -0.9504845 7.21083e-10 0.6078575 -0.7622294 2.68537e-9 0.8783797 -0.4230054 -1.49521e-8 0.9749279 9.89275e-9 3.31305e-8 0.8783796 0.4230055 6.33846e-10 0.6078573 0.7622295 2.8277e-9 0.2169413 0.9504845 1.9462e-9 -0.2169426 0.9504842 3.51617e-9 -0.6078584 0.7622287 -1.28249e-8 -0.8783801 0.4230047 -3.55722e-8 -0.974928 0.01037875 -0.04692358 -0.9749279 -0.01037927 0.04692359 -0.9749279 -0.01037929 -0.04692363 -0.8783796 -0.4230055 7.11577e-9 -0.9749279 -0.01037927 0.04692359 -0.6078576 -0.7622294 -5.38627e-9 -0.2169418 -0.9504845 -4.0761e-8 0.2169417 -0.9504845 -6.37691e-9 0.6078574 -0.7622295 4.32713e-8 0.8783797 -0.4230054 4.91462e-8 0.9749279 7.86884e-9 6.87766e-8 0.8783796 0.4230055 7.54338e-8 0.6078573 0.7622295 -3.74104e-9 0.2169413 0.9504845 -6.87179e-10 -0.2169426 0.9504843 -5.06594e-9 -0.6078584 0.7622287 1.11192e-8 -0.8783801 0.4230047 -7.55682e-8 -0.974928 0.01037875 0.04692348 -0.9749279 -0.01037935 -0.04692387 -0.9749279 -0.08239562 -0.107451 -0.8783797 -0.4230055 5.99473e-9 -0.9749279 -0.01037929 -0.04692363 -0.6078575 -0.7622295 -5.14789e-8 -0.2169418 -0.9504844 -6.99784e-8 0.2169419 -0.9504845 -6.77193e-8 0.6078575 -0.7622295 8.75301e-8 0.8783796 -0.4230056 2.38485e-7 0.9749279 -3.58523e-8 7.51737e-8 0.8783796 0.4230055 1.39721e-9 0.6078573 0.7622295 6.92574e-8 0.2169412 0.9504845 -5.31509e-8 -0.2169425 0.9504843 -5.50918e-9 -0.6078583 0.7622288 -2.65355e-8 -0.8605051 0.4601225 0.05372539 -0.974928 0.08239491 0.1074507 -0.9749278 -0.08239586 -0.1074513 -0.9749279 0.03512977 0.0181467 -0.8936222 -0.3913543 0.01814683 -0.9749279 -0.08239562 -0.107451 -0.6353234 -0.740326 0.01814686 -0.2511912 -0.9426673 0.01814672 0.1826924 -0.9583017 0.01814681 0.5803915 -0.7841328 0.01814685 0.8631371 -0.454657 0.01814701 0.9749278 -0.03513059 0.01814698 0.8936221 0.3913542 0.01814686 0.6353234 0.7403261 0.01814691 0.2511908 0.9426673 0.01814683 -0.1826932 0.9583015 0.01814677 -0.5803923 0.7841322 0.01814686 -0.802168 0.5812611 0.09073409 -0.91413 0.3487692 -0.1074508 -0.9545922 0.2647484 -0.09073412 -0.91413 0.3487692 -0.1074508 -0.974928 0.08239496 0.1074508 -0.9749278 -0.2225216 -1.94415e-8 -0.7818317 -0.6234895 -3.91125e-9 -0.4338831 -0.9009692 1.23937e-8 -6.80542e-16 -1 2.62439e-8 0.4338836 -0.9009689 3.48962e-8 0.7818314 -0.6234899 3.66368e-8 0.9749278 -0.2225216 3.11211e-8 0.9749279 0.222521 1.94415e-8 0.7818313 0.6234901 3.91122e-9 0.4338837 0.9009689 -1.23937e-8 -1.19783e-6 1 -2.62439e-8 -0.4338844 0.9009685 -3.48962e-8 -0.7818321 0.623489 -3.66368e-8 -0.974928 0.2225205 -3.11211e-8 -0.9545922 0.2647484 -0.09073412 + + + + + + + + + + -0.2225218 0.9749277 -2.30205e-8 -0.04052453 0.8877353 -0.4585672 -0.4216851 0.7822389 -0.4585672 -0.6234899 0.7818314 -3.58345e-9 -0.7193257 0.5218109 -0.4585672 -0.9009689 0.4338837 1.65634e-8 -0.8744953 0.1580317 -0.4585672 -1 0 3.34297e-8 -0.8564605 -0.2370475 -0.4585672 -0.900969 -0.4338836 4.36748e-8 -0.6687932 -0.5851767 -0.4585672 -0.6234899 -0.7818314 4.52696e-8 -0.3486635 -0.8174044 -0.4585672 -0.2225219 -0.9749277 3.78982e-8 0.04052361 -0.8877353 -0.4585672 0.222521 -0.9749279 2.30206e-8 0.4216849 -0.7822391 -0.4585672 0.62349 -0.7818313 3.58345e-9 0.7193259 -0.5218107 -0.4585672 0.9009691 -0.4338832 -1.65634e-8 0.8744955 -0.1580312 -0.4585672 1 7.40827e-7 -3.34297e-8 0.8564603 0.2370483 -0.4585672 0.9009686 0.4338844 -4.36748e-8 0.6687926 0.5851774 -0.4585672 0.623489 0.7818321 -4.52696e-8 0.445375 0.7693955 -0.4578992 0.2225204 0.974928 -3.78982e-8 0.1987407 0.8707421 -0.4497891 -0.1375673 0.6027217 -0.7860037 -0.2640215 0.5482458 -0.7935484 -0.4757501 0.3793979 -0.7935484 -0.5932505 0.1354056 -0.7935484 -0.5932505 -0.1354055 -0.7935485 -0.4757501 -0.3793979 -0.7935484 -0.2640214 -0.5482459 -0.7935484 -1.5735e-7 -0.6085071 -0.7935484 0.2640215 -0.5482459 -0.7935484 0.4757502 -0.3793978 -0.7935484 0.5932507 -0.1354052 -0.7935484 0.5932505 0.135406 -0.7935484 0.4757496 0.3793984 -0.7935485 0.2640209 0.5482461 -0.7935484 0.1375669 0.6027217 -0.7860037 0.1987407 0.8707421 -0.4497891 0.137567 0.6027217 -0.7860037 -0.1375673 0.6027217 -0.7860037 -0.04923392 0.2157078 -0.975216 -0.09370578 0.194582 -0.9764001 -0.1375673 0.6027217 -0.7860037 -0.168852 0.1346549 -0.9764001 -0.2105549 0.04805777 -0.9764001 -0.2105549 -0.04805776 -0.9764001 -0.168852 -0.1346549 -0.9764001 -0.09370578 -0.194582 -0.9764001 3.5372e-8 -0.2159697 -0.9764001 0.09370579 -0.194582 -0.9764001 0.168852 -0.1346549 -0.9764 0.210555 -0.04805767 -0.9764 0.2105549 0.04805796 -0.9764 0.1688518 0.1346551 -0.9764001 0.09370564 0.1945821 -0.9764001 0.04923379 0.2157078 -0.975216 -0.04923392 0.2157078 -0.975216 0.04923396 -0.215708 -0.9752159 0.09370587 -0.1945822 -0.9764 -0.04923392 0.2157078 -0.975216 0.1688521 -0.134655 -0.9764 0.2105551 -0.04805776 -0.9764 0.210555 0.04805779 -0.9764 0.1688521 0.134655 -0.9764 0.0937059 0.1945822 -0.9764 6.71373e-8 0.2159699 -0.9764 -0.09370582 0.1945822 -0.9764 -0.1688522 0.134655 -0.9764 -0.2105552 0.04805771 -0.9764 -0.210555 -0.04805798 -0.9764 -0.1688519 -0.1346552 -0.9764 -0.09370562 -0.1945823 -0.9764 -0.0492337 -0.215708 -0.9752159 0.04923421 -0.215708 -0.9752159 0.1375676 -0.6027218 -0.7860035 0.2640215 -0.5482461 -0.7935483 0.04923396 -0.215708 -0.9752159 0.4757503 -0.379398 -0.7935483 0.5932507 -0.1354055 -0.7935483 0.5932507 0.1354057 -0.7935483 0.4757503 0.379398 -0.7935482 0.2640218 0.548246 -0.7935483 8.35655e-8 0.6085073 -0.7935483 -0.2640215 0.5482461 -0.7935483 -0.4757503 0.379398 -0.7935483 -0.5932509 0.1354052 -0.7935483 -0.5932507 -0.135406 -0.7935483 -0.4757498 -0.3793985 -0.7935483 -0.3258031 -0.5168711 -0.7916417 -0.1375669 -0.6027219 -0.7860036 0.137568 -0.6027218 -0.7860035 -0.04052343 -0.8877354 -0.458567 0.3486632 -0.8174045 -0.4585671 0.1375676 -0.6027218 -0.7860035 0.6687933 -0.5851768 -0.458567 0.8564606 -0.2370474 -0.4585671 0.8744955 0.1580318 -0.458567 0.7193258 0.5218109 -0.458567 0.4216851 0.782239 -0.458567 0.04052431 0.8877353 -0.4585671 -0.3486632 0.8174045 -0.4585671 -0.6687933 0.5851767 -0.458567 -0.8564607 0.2370471 -0.4585671 -0.8744953 -0.1580326 -0.458567 -0.7193253 -0.5218116 -0.4585671 -0.5568597 -0.6982815 -0.449789 -0.137567 -0.6027219 -0.7860035 -0.1987408 -0.8707422 -0.449789 -0.137567 -0.6027219 -0.7860035 -0.137567 -0.6027219 -0.7860036 0.2225216 -0.9749278 3.13562e-8 0.6234895 -0.7818317 3.66863e-8 0.9009692 -0.4338831 3.47502e-8 1 0 2.59315e-8 0.9009689 0.4338836 1.19766e-8 0.6234899 0.7818314 -4.35029e-9 0.2225216 0.9749278 -1.98156e-8 -0.222521 0.9749279 -3.13562e-8 -0.6234901 0.7818313 -3.66863e-8 -0.9009689 0.4338837 -3.47502e-8 -1 -1.19783e-6 -2.59314e-8 -0.9009685 -0.4338844 -1.19766e-8 -0.623489 -0.7818321 4.35033e-9 -0.2225205 -0.974928 1.98156e-8 -0.1987408 -0.8707422 -0.449789 + + + + + + + + + + + + + + + + + +

0 0 86 0 1 1 1 1 2 2 2 2 0 0 87 3 2 2 2 2 3 3 3 4 0 0 88 5 3 3 3 4 4 4 4 6 0 0 89 7 4 4 4 6 5 5 5 8 0 0 90 9 5 5 5 8 6 6 6 10 0 0 91 11 6 6 6 10 7 7 7 12 0 0 92 13 7 7 7 12 8 8 8 14 0 0 93 15 8 8 8 14 9 9 9 16 0 0 94 17 9 9 9 16 10 10 10 18 0 0 95 19 10 10 10 18 11 11 11 20 0 0 96 21 11 11 11 20 12 12 12 22 0 0 97 23 12 12 12 22 13 13 13 24 0 0 98 25 13 13 13 24 14 14 14 26 0 0 0 27 14 14 99 28 1 1 1 1 1 1 1 1 15 15 100 29 16 16 16 30 1 1 1 1 16 16 16 30 2 2 2 2 2 2 2 2 16 16 16 30 17 17 17 31 2 2 2 2 17 17 17 31 3 3 3 4 3 3 3 4 17 17 17 31 18 18 18 32 3 3 3 4 18 18 18 32 4 4 4 6 4 4 4 6 18 18 18 32 19 19 19 33 4 4 4 6 19 19 19 33 5 5 5 8 5 5 5 8 19 19 19 33 20 20 20 34 5 5 5 8 20 20 20 34 6 6 6 10 6 6 6 10 20 20 20 34 21 21 21 35 6 6 6 10 21 21 21 35 7 7 7 12 7 7 7 12 21 21 21 35 22 22 22 36 7 7 7 12 22 22 22 36 8 8 8 14 8 8 8 14 22 22 22 36 23 23 23 37 8 8 8 14 23 23 23 37 9 9 9 16 9 9 9 16 23 23 23 37 24 24 24 38 9 9 9 16 24 24 24 38 10 10 10 18 10 10 10 18 24 24 24 38 25 25 25 39 10 10 10 18 25 25 25 39 11 11 11 20 11 11 11 20 25 25 25 39 26 26 26 40 11 11 11 20 26 26 26 40 12 12 12 22 12 12 12 22 26 26 26 40 27 27 27 41 12 12 12 22 27 27 27 41 13 13 13 24 13 13 13 24 27 27 27 41 28 28 28 42 13 13 13 24 28 28 28 42 14 14 14 26 14 14 14 26 28 28 28 42 15 15 15 43 14 14 101 44 15 15 102 45 1 1 1 1 15 15 103 46 29 29 104 47 30 30 30 48 15 15 105 49 30 30 30 48 16 16 16 30 16 16 16 30 30 30 30 48 31 31 31 50 16 16 16 30 31 31 31 50 17 17 17 31 17 17 17 31 31 31 31 50 32 32 32 51 17 17 17 31 32 32 32 51 18 18 18 32 18 18 18 32 32 32 32 51 33 33 33 52 18 18 18 32 33 33 33 52 19 19 19 33 19 19 19 33 33 33 33 52 34 34 34 53 19 19 19 33 34 34 34 53 20 20 20 34 20 20 20 34 34 34 34 53 35 35 35 54 20 20 20 34 35 35 35 54 21 21 21 35 21 21 21 35 35 35 35 54 36 36 36 55 21 21 21 35 36 36 36 55 22 22 22 36 22 22 22 36 36 36 36 55 37 37 37 56 22 22 22 36 37 37 37 56 23 23 23 37 23 23 23 37 37 37 37 56 38 38 38 57 23 23 23 37 38 38 38 57 24 24 24 38 24 24 24 38 38 38 38 57 39 39 39 58 24 24 24 38 39 39 39 58 25 25 25 39 25 25 25 39 39 39 39 58 40 40 40 59 25 25 25 39 40 40 40 59 26 26 26 40 26 26 26 40 40 40 40 59 41 41 41 60 26 26 26 40 41 41 41 60 27 27 27 41 27 27 27 41 41 41 41 60 42 42 42 61 27 27 27 41 42 42 42 61 28 28 28 42 28 28 28 42 42 42 42 61 29 29 29 62 28 28 28 42 29 29 29 62 15 15 15 43 29 29 106 63 43 43 107 64 44 44 44 65 29 29 108 66 44 44 44 65 30 30 30 48 30 30 30 48 44 44 44 65 45 45 45 67 30 30 30 48 45 45 45 67 31 31 31 50 31 31 31 50 45 45 45 67 46 46 46 68 31 31 31 50 46 46 46 68 32 32 32 51 32 32 32 51 46 46 46 68 47 47 47 69 32 32 32 51 47 47 47 69 33 33 33 52 33 33 33 52 47 47 47 69 48 48 48 70 33 33 33 52 48 48 48 70 34 34 34 53 34 34 34 53 48 48 48 70 49 49 49 71 34 34 34 53 49 49 49 71 35 35 35 54 35 35 35 54 49 49 49 71 50 50 50 72 35 35 35 54 50 50 50 72 36 36 36 55 36 36 36 55 50 50 50 72 51 51 51 73 36 36 36 55 51 51 51 73 37 37 37 56 37 37 37 56 51 51 51 73 52 52 52 74 37 37 37 56 52 52 52 74 38 38 38 57 38 38 38 57 52 52 52 74 53 53 53 75 38 38 38 57 53 53 53 75 39 39 39 58 39 39 39 58 53 53 53 75 54 54 54 76 39 39 39 58 54 54 54 76 40 40 40 59 40 40 40 59 54 54 54 76 55 55 55 77 40 40 40 59 55 55 55 77 41 41 41 60 41 41 41 60 55 55 55 77 56 56 56 78 41 41 41 60 56 56 56 78 42 42 42 61 42 42 42 61 56 56 56 78 43 43 43 79 42 42 42 61 43 43 43 79 29 29 29 62 43 43 109 80 57 57 110 81 58 58 58 82 43 43 111 83 58 58 58 82 44 44 44 65 44 44 44 65 58 58 58 82 59 59 59 84 44 44 44 65 59 59 59 84 45 45 45 67 45 45 45 67 59 59 59 84 60 60 60 85 45 45 45 67 60 60 60 85 46 46 46 68 46 46 46 68 60 60 60 85 61 61 61 86 46 46 46 68 61 61 61 86 47 47 47 69 47 47 47 69 61 61 61 86 62 62 62 87 47 47 47 69 62 62 62 87 48 48 48 70 48 48 48 70 62 62 62 87 63 63 63 88 48 48 48 70 63 63 63 88 49 49 49 71 49 49 49 71 63 63 63 88 64 64 64 89 49 49 49 71 64 64 64 89 50 50 50 72 50 50 50 72 64 64 64 89 65 65 65 90 50 50 50 72 65 65 65 90 51 51 51 73 51 51 51 73 65 65 65 90 66 66 66 91 51 51 51 73 66 66 66 91 52 52 52 74 52 52 52 74 66 66 66 91 67 67 67 92 52 52 52 74 67 67 67 92 53 53 53 75 53 53 53 75 67 67 67 92 68 68 68 93 53 53 53 75 68 68 68 93 54 54 54 76 54 54 54 76 68 68 68 93 69 69 69 94 54 54 54 76 69 69 69 94 55 55 55 77 55 55 55 77 69 69 69 94 70 70 70 95 55 55 55 77 70 70 70 95 56 56 56 78 56 56 56 78 70 70 70 95 57 57 57 96 56 56 56 78 57 57 57 96 43 43 43 79 57 57 112 97 71 71 71 98 72 72 72 99 57 57 113 100 72 72 72 99 58 58 58 82 58 58 58 82 72 72 72 99 73 73 73 101 58 58 58 82 73 73 73 101 59 59 59 84 59 59 59 84 73 73 73 101 74 74 74 102 59 59 59 84 74 74 74 102 60 60 60 85 60 60 60 85 74 74 74 102 75 75 75 103 60 60 60 85 75 75 75 103 61 61 61 86 61 61 61 86 75 75 75 103 76 76 76 104 61 61 61 86 76 76 76 104 62 62 62 87 62 62 62 87 76 76 76 104 77 77 77 105 62 62 62 87 77 77 77 105 63 63 63 88 63 63 63 88 77 77 77 105 78 78 78 106 63 63 63 88 78 78 78 106 64 64 64 89 64 64 64 89 78 78 78 106 79 79 79 107 64 64 64 89 79 79 79 107 65 65 65 90 65 65 65 90 79 79 79 107 80 80 80 108 65 65 65 90 80 80 80 108 66 66 66 91 66 66 66 91 80 80 80 108 81 81 81 109 66 66 66 91 81 81 81 109 67 67 67 92 67 67 67 92 81 81 81 109 82 82 82 110 67 67 67 92 82 82 82 110 68 68 68 93 68 68 68 93 82 82 82 110 83 83 83 111 68 68 68 93 83 83 83 111 69 69 69 94 69 69 69 94 83 83 83 111 84 84 84 112 69 69 69 94 84 84 84 112 70 70 70 95 70 70 114 113 84 84 115 114 71 71 71 98 70 70 116 115 71 71 71 98 57 57 117 116 85 85 118 117 72 72 72 99 71 71 71 98 85 85 119 118 73 73 73 101 72 72 72 99 85 85 120 119 74 74 74 102 73 73 73 101 85 85 121 120 75 75 75 103 74 74 74 102 85 85 122 121 76 76 76 104 75 75 75 103 85 85 123 122 77 77 77 105 76 76 76 104 85 85 124 123 78 78 78 106 77 77 77 105 85 85 125 124 79 79 79 107 78 78 78 106 85 85 126 125 80 80 80 108 79 79 79 107 85 85 127 126 81 81 81 109 80 80 80 108 85 85 128 127 82 82 82 110 81 81 81 109 85 85 129 128 83 83 83 111 82 82 82 110 85 85 130 129 84 84 84 112 83 83 83 111 85 85 85 130 71 71 71 98 84 84 131 131

+
+
+
+ + + + 0 0 5 -1.28465e-7 2.938926 4.045085 -1.727457 2.377641 4.045085 -2.795085 0.9081783 4.045085 -2.795085 -0.9081781 4.045085 -1.727458 -2.377641 4.045085 3.50463e-8 -2.938926 4.045085 1.727458 -2.377641 4.045085 2.795085 -0.9081767 4.045085 2.795084 0.9081804 4.045085 1.727455 2.377643 4.045085 -2.0786e-7 4.755283 1.545085 -2.795085 3.847105 1.545085 -4.522542 1.469463 1.545085 -4.522543 -1.469463 1.545085 -2.795086 -3.847104 1.545085 5.67062e-8 -4.755283 1.545085 2.795086 -3.847104 1.545085 4.522543 -1.469461 1.545085 4.522542 1.469467 1.545085 2.795081 3.847108 1.545085 -2.0786e-7 4.755282 -1.545085 -2.795084 3.847104 -1.545085 -4.522542 1.469463 -1.545085 -4.522542 -1.469463 -1.545085 -2.795086 -3.847103 -1.545085 5.67062e-8 -4.755282 -1.545085 2.795086 -3.847103 -1.545085 4.522543 -1.469461 -1.545085 4.522542 1.469467 -1.545085 2.795081 3.847107 -1.545085 -1.28465e-7 2.938926 -4.045085 -1.727457 2.377641 -4.045085 -2.795085 0.9081782 -4.045085 -2.795085 -0.908178 -4.045085 -1.727458 -2.37764 -4.045085 3.50463e-8 -2.938926 -4.045085 1.727458 -2.37764 -4.045085 2.795085 -0.9081766 -4.045085 2.795084 0.9081803 -4.045085 1.727455 2.377643 -4.045085 0 0 -5 + + + + + + + + + + 0 -2.11156e-8 1 -2.11354e-7 0.6252257 0.780444 -0.3674984 0.5058182 0.780444 -0.5946249 0.1932054 0.780444 -0.594625 -0.1932053 0.780444 -0.3674985 -0.5058182 0.780444 1.05677e-8 -0.6252257 0.780444 0.3674986 -0.5058181 0.780444 0.5946251 -0.1932051 0.780444 0.5946248 0.1932058 0.780444 0.3674981 0.5058185 0.780444 -2.70626e-7 0.9562714 0.2924812 -0.5620822 0.7736397 0.2924811 -0.909468 0.2955042 0.2924811 -0.9094681 -0.295504 0.2924812 -0.5620822 -0.7736397 0.2924811 5.52297e-9 -0.9562714 0.2924811 0.5620824 -0.7736396 0.2924811 0.9094682 -0.2955036 0.2924811 0.9094678 0.2955048 0.2924812 0.5620816 0.7736402 0.2924811 -2.70626e-7 0.9562712 -0.2924812 -0.5620822 0.7736397 -0.2924812 -0.909468 0.2955042 -0.2924812 -0.9094681 -0.295504 -0.2924812 -0.5620822 -0.7736396 -0.2924812 2.76149e-9 -0.9562712 -0.2924812 0.5620824 -0.7736395 -0.2924812 0.9094682 -0.2955037 -0.2924812 0.9094678 0.2955048 -0.2924812 0.5620815 0.7736402 -0.2924812 -1.638e-7 0.6252257 -0.780444 -0.3674985 0.5058182 -0.780444 -0.594625 0.1932054 -0.780444 -0.594625 -0.1932053 -0.780444 -0.3674985 -0.5058182 -0.780444 -3.17032e-8 -0.6252257 -0.780444 0.3674986 -0.5058181 -0.780444 0.5946251 -0.1932051 -0.780444 0.5946248 0.1932058 -0.780444 0.3674981 0.5058185 -0.780444 1.51768e-8 -4.22312e-8 -1 + + + + + + + + + + 0.95 1 0.999001 0.9999999 0.8 0.9990011 0.09999996 0.8 0.999001 0.2 0.8 0.999001 0.3 0.8 0.999001 0.3999999 0.8 0.999001 0.5 0.8 0.9990011 0.6 0.8 0.9990011 0.7 0.8 0.9990011 0.8000001 0.8 0.9990011 0.9000001 0.8 0.9990011 0.9999999 0.6 0.9990011 0.09999996 0.6 0.999001 0.2 0.6 0.999001 0.3 0.6 0.9990011 0.3999999 0.6 0.999001 0.5 0.6 0.9990011 0.6 0.6 0.9990011 0.7 0.6 0.9990011 0.8000001 0.6 0.9990011 0.9000001 0.6 0.9990011 0.9999999 0.4 0.9990011 0.09999996 0.4 0.999001 0.2 0.4 0.9990009 0.3 0.4 0.999001 0.3999999 0.4 0.999001 0.5 0.4 0.9990011 0.6 0.4 0.999001 0.7 0.4 0.9990011 0.8000001 0.4 0.9990011 0.9000001 0.4 0.999001 0.9999999 0.2 0.9990011 0.09999996 0.2 0.999001 0.2 0.2 0.999001 0.3 0.2 0.999001 0.3999999 0.2 0.999001 0.5 0.2 0.9990011 0.6 0.2 0.9990011 0.7 0.2 0.9990011 0.8000001 0.2 0.9990011 0.9000001 0.2 0.9990011 0.95 0 0.999001 0.04999995 1 0.999001 -5.96046e-8 0.8 0.9990011 0.15 1 0.999001 0.25 1 0.999001 0.35 1 0.999001 0.45 1 0.999001 0.55 1 0.999001 0.65 1 0.999001 0.7500001 1 0.999001 0.8500001 1 0.999001 -5.96046e-8 0.8 0.9990011 -5.96046e-8 0.6 0.9990011 -5.96046e-8 0.8 0.9990011 -5.96046e-8 0.6 0.9990011 -5.96046e-8 0.4 0.9990011 -5.96046e-8 0.6 0.9990011 -5.96046e-8 0.4 0.9990011 -5.96046e-8 0.2 0.9990011 -5.96046e-8 0.4 0.9990011 0.04999995 0 0.999001 -5.96046e-8 0.2 0.9990011 0.15 0 0.999001 0.25 0 0.999001 0.35 0 0.999001 0.45 0 0.999001 0.55 0 0.999001 0.65 0 0.999001 0.7500001 0 0.999001 0.8500001 0 0.999001 + + + + + + + + + + -0.9510565 -0.3090171 -6.52508e-9 -0.9510565 -0.1882202 0.1507859 -0.7472943 -0.5894716 0.03015721 -0.5877853 -0.809017 -1.70829e-8 -0.2580911 -0.9161411 0.03015718 -1.31262e-7 -1 -2.11156e-8 0.3296941 -0.8928759 0.0301572 0.5877852 -0.809017 -1.70829e-8 0.7915474 -0.5285625 0.03015717 0.9510565 -0.3090172 -6.52508e-9 0.9510565 0.03764403 0.0301572 0.9510565 0.3090172 6.52508e-9 0.7472941 0.5894718 0.03015722 0.587785 0.8090172 1.70829e-8 0.2580906 0.9161412 0.03015722 -6.5631e-7 1 2.11156e-8 -0.3296949 0.8928756 0.0301572 -0.587786 0.8090165 1.70829e-8 -0.7915479 0.5285618 0.03015718 -0.9510567 0.3090165 6.52507e-9 -0.9510567 0.1882196 -0.1507859 -0.9510565 -0.1882202 0.1507858 -0.9510566 -0.02643514 0.08642931 -0.7694209 -0.559017 7.9155e-10 -0.9510565 -0.1882202 0.1507859 -0.2938927 -0.9045085 -4.0073e-9 0.2938925 -0.9045085 -7.41231e-10 0.7694209 -0.5590171 -6.34775e-9 0.9510565 4.77085e-9 -2.3606e-9 0.7694207 0.5590172 -6.72929e-9 0.2938922 0.9045086 -2.86915e-9 -0.2938933 0.9045082 -2.409e-9 -0.7694214 0.5590164 -1.96186e-8 -0.9510566 0.0264346 -0.0864293 -0.9510566 -0.02643514 0.08642932 -0.9510566 -0.02643521 -0.08642933 -0.7694209 -0.559017 1.17386e-8 -0.9510566 -0.02643514 0.08642931 -0.2938927 -0.9045085 -3.27196e-9 0.2938926 -0.9045085 1.16709e-8 0.7694209 -0.5590171 -4.67655e-8 0.9510565 2.37408e-9 1.21738e-9 0.7694207 0.5590172 1.66922e-8 0.2938922 0.9045086 -7.07564e-9 -0.2938933 0.9045082 1.75192e-8 -0.7694214 0.5590164 1.41495e-8 -0.9510566 0.02643467 0.08642932 -0.9510567 -0.0264351 -0.08642897 -0.9510566 -0.1882199 -0.1507856 -0.7915475 -0.5285623 0.03015731 -0.9510566 -0.02643521 -0.08642933 -0.3296942 -0.8928758 0.03015721 0.258091 -0.9161412 0.03015719 0.7472943 -0.5894716 0.03015712 0.9510565 -0.03764394 0.03015711 0.7915473 0.5285625 0.03015719 0.3296938 0.892876 0.03015719 -0.2580917 0.9161409 0.03015721 -0.7472948 0.5894711 0.03015717 -0.9510567 0.1882196 0.1507858 -0.9510566 -0.3090166 -1.38389e-9 -0.9510567 -0.1882199 -0.1507856 -0.5877853 -0.8090169 2.5245e-8 -6.40936e-16 -1 4.22312e-8 0.5877852 -0.809017 4.30865e-8 0.9510566 -0.3090169 2.74842e-8 0.9510565 0.3090172 1.38386e-9 0.587785 0.8090172 -2.5245e-8 -6.5631e-7 1 -4.22312e-8 -0.587786 0.8090165 -4.30865e-8 -0.9510567 0.3090165 -2.74842e-8 + + + + + + + + + + -0.3090171 0.9510565 2.00821e-8 -0.2458018 0.7565 -0.6060439 -0.4991224 0.60081 -0.6244231 -0.809017 0.5877853 1.24114e-8 -0.7569457 0.1926888 -0.6244232 -1 1.31262e-7 2.77168e-15 -0.7256415 -0.289033 -0.6244231 -0.809017 -0.5877852 -1.24114e-8 -0.417167 -0.660354 -0.6244232 -0.3090172 -0.9510565 -2.00821e-8 0.05065125 -0.7794422 -0.6244232 0.3090172 -0.9510565 -2.00821e-8 0.4991225 -0.6008098 -0.6244231 0.8090172 -0.587785 -1.24114e-8 0.7569458 -0.1926883 -0.6244232 1 6.5631e-7 1.38584e-14 0.7256413 0.2890337 -0.6244231 0.8090165 0.587786 1.24115e-8 0.4171665 0.6603543 -0.6244232 0.3090165 0.9510567 2.00821e-8 0.2458013 0.7565002 -0.6060439 -0.2458018 0.7565 -0.6060439 -0.09460663 0.2911693 -0.9519822 -0.1719161 0.2366222 -0.9562713 -0.2458018 0.7565 -0.6060439 -0.2781661 0.09038166 -0.9562713 -0.2781661 -0.09038162 -0.9562713 -0.1719161 -0.2366222 -0.9562713 -9.06353e-10 -0.2924811 -0.9562713 0.1719161 -0.2366221 -0.9562713 0.2781661 -0.09038149 -0.9562713 0.2781661 0.09038187 -0.9562713 0.1719159 0.2366223 -0.9562713 0.09460646 0.2911693 -0.9519822 -0.09460664 0.2911693 -0.9519822 0.09460667 -0.2911693 -0.9519821 0.1719161 -0.2366223 -0.9562713 -0.09460663 0.2911693 -0.9519822 0.2781661 -0.09038168 -0.9562713 0.2781662 0.09038165 -0.9562713 0.1719161 0.2366223 -0.9562713 4.93948e-10 0.2924813 -0.9562713 -0.1719162 0.2366222 -0.9562713 -0.2781662 0.09038154 -0.9562713 -0.2781661 -0.09038187 -0.9562713 -0.171916 -0.2366224 -0.9562713 -0.09460649 -0.2911694 -0.9519821 0.09460628 -0.2911694 -0.9519822 0.2458014 -0.7565001 -0.6060439 0.4171668 -0.6603542 -0.6244231 0.09460667 -0.2911693 -0.9519821 0.7256415 -0.2890331 -0.6244232 0.7569457 0.1926887 -0.6244231 0.4991223 0.60081 -0.6244231 0.05065112 0.7794422 -0.6244232 -0.4171671 0.6603539 -0.6244232 -0.7256416 0.2890327 -0.6244232 -0.7569456 -0.1926893 -0.6244231 -0.4991219 -0.6008104 -0.6244231 -0.2458013 -0.7565002 -0.6060439 0.3090166 -0.9510566 4.48541e-8 0.2458014 -0.7565001 -0.6060439 0.8090169 -0.5877853 3.71012e-8 1 0 1.51768e-8 0.809017 0.5877852 -1.25446e-8 0.3090169 0.9510566 -3.54744e-8 -0.3090172 0.9510565 -4.48541e-8 -0.8090172 0.587785 -3.71012e-8 -1 -6.5631e-7 -1.51768e-8 -0.8090165 -0.587786 1.25446e-8 -0.3090165 -0.9510567 3.54744e-8 + + + + + + + + + + + + + + + + + +

0 0 42 0 1 1 43 1 2 2 2 2 0 0 44 3 2 2 2 2 3 3 3 4 0 0 45 5 3 3 3 4 4 4 4 6 0 0 46 7 4 4 4 6 5 5 5 8 0 0 47 9 5 5 5 8 6 6 6 10 0 0 48 11 6 6 6 10 7 7 7 12 0 0 49 13 7 7 7 12 8 8 8 14 0 0 50 15 8 8 8 14 9 9 9 16 0 0 51 17 9 9 9 16 10 10 10 18 0 0 0 19 10 10 10 18 1 1 1 20 1 1 52 21 11 11 53 22 12 12 12 23 1 1 54 24 12 12 12 23 2 2 2 2 2 2 2 2 12 12 12 23 13 13 13 25 2 2 2 2 13 13 13 25 3 3 3 4 3 3 3 4 13 13 13 25 14 14 14 26 3 3 3 4 14 14 14 26 4 4 4 6 4 4 4 6 14 14 14 26 15 15 15 27 4 4 4 6 15 15 15 27 5 5 5 8 5 5 5 8 15 15 15 27 16 16 16 28 5 5 5 8 16 16 16 28 6 6 6 10 6 6 6 10 16 16 16 28 17 17 17 29 6 6 6 10 17 17 17 29 7 7 7 12 7 7 7 12 17 17 17 29 18 18 18 30 7 7 7 12 18 18 18 30 8 8 8 14 8 8 8 14 18 18 18 30 19 19 19 31 8 8 8 14 19 19 19 31 9 9 9 16 9 9 9 16 19 19 19 31 20 20 20 32 9 9 9 16 20 20 20 32 10 10 10 18 10 10 10 18 20 20 20 32 11 11 11 33 10 10 10 18 11 11 11 33 1 1 1 20 11 11 55 34 21 21 56 35 22 22 22 36 11 11 57 37 22 22 22 36 12 12 12 23 12 12 12 23 22 22 22 36 23 23 23 38 12 12 12 23 23 23 23 38 13 13 13 25 13 13 13 25 23 23 23 38 24 24 24 39 13 13 13 25 24 24 24 39 14 14 14 26 14 14 14 26 24 24 24 39 25 25 25 40 14 14 14 26 25 25 25 40 15 15 15 27 15 15 15 27 25 25 25 40 26 26 26 41 15 15 15 27 26 26 26 41 16 16 16 28 16 16 16 28 26 26 26 41 27 27 27 42 16 16 16 28 27 27 27 42 17 17 17 29 17 17 17 29 27 27 27 42 28 28 28 43 17 17 17 29 28 28 28 43 18 18 18 30 18 18 18 30 28 28 28 43 29 29 29 44 18 18 18 30 29 29 29 44 19 19 19 31 19 19 19 31 29 29 29 44 30 30 30 45 19 19 19 31 30 30 30 45 20 20 20 32 20 20 20 32 30 30 30 45 21 21 21 46 20 20 20 32 21 21 21 46 11 11 11 33 21 21 58 47 31 31 59 48 32 32 32 49 21 21 60 50 32 32 32 49 22 22 22 36 22 22 22 36 32 32 32 49 33 33 33 51 22 22 22 36 33 33 33 51 23 23 23 38 23 23 23 38 33 33 33 51 34 34 34 52 23 23 23 38 34 34 34 52 24 24 24 39 24 24 24 39 34 34 34 52 35 35 35 53 24 24 24 39 35 35 35 53 25 25 25 40 25 25 25 40 35 35 35 53 36 36 36 54 25 25 25 40 36 36 36 54 26 26 26 41 26 26 26 41 36 36 36 54 37 37 37 55 26 26 26 41 37 37 37 55 27 27 27 42 27 27 27 42 37 37 37 55 38 38 38 56 27 27 27 42 38 38 38 56 28 28 28 43 28 28 28 43 38 38 38 56 39 39 39 57 28 28 28 43 39 39 39 57 29 29 29 44 29 29 29 44 39 39 39 57 40 40 40 58 29 29 29 44 40 40 40 58 30 30 30 45 30 30 30 45 40 40 40 58 31 31 31 59 30 30 30 45 31 31 31 59 21 21 21 46 41 41 61 60 32 32 32 49 31 31 62 61 41 41 63 62 33 33 33 51 32 32 32 49 41 41 64 63 34 34 34 52 33 33 33 51 41 41 65 64 35 35 35 53 34 34 34 52 41 41 66 65 36 36 36 54 35 35 35 53 41 41 67 66 37 37 37 55 36 36 36 54 41 41 68 67 38 38 38 56 37 37 37 55 41 41 69 68 39 39 39 57 38 38 38 56 41 41 70 69 40 40 40 58 39 39 39 57 41 41 41 70 31 31 31 59 40 40 40 58

+
+
+
+ + + + 0 0 5 -1.54543e-7 3.535534 3.535534 -2.5 2.5 3.535534 -3.535534 -3.09086e-7 3.535534 -2.5 -2.5 3.535534 4.21608e-8 -3.535534 3.535534 2.5 -2.5 3.535534 3.535534 -1.0677e-6 3.535534 2.500001 2.499999 3.535534 -2.18557e-7 5 -2.18557e-7 -3.535534 3.535534 -2.18557e-7 -5 -4.37114e-7 -2.18557e-7 -3.535533 -3.535534 -2.18557e-7 5.96244e-8 -5 -2.18557e-7 3.535533 -3.535534 -2.18557e-7 5 -1.50996e-6 -2.18557e-7 3.535536 3.535532 -2.18557e-7 -1.54543e-7 3.535534 -3.535534 -2.5 2.5 -3.535534 -3.535534 -3.09086e-7 -3.535534 -2.5 -2.5 -3.535534 4.21608e-8 -3.535534 -3.535534 2.5 -2.5 -3.535534 3.535534 -1.0677e-6 -3.535534 2.500001 2.499999 -3.535534 0 0 -5 + + + + + + + + + + 9.88537e-9 3.95415e-8 1 6.9026e-8 0.7486158 0.6630041 -0.5293514 0.5293513 0.6630041 -0.7486159 -8.05304e-8 0.6630041 -0.5293513 -0.5293514 0.663004 1.15043e-8 -0.7486158 0.6630041 0.5293513 -0.5293514 0.6630041 0.7486158 -2.01326e-7 0.6630041 0.5293515 0.5293512 0.6630041 7.27742e-8 1 -1.2129e-8 -0.7071067 0.7071068 -3.63871e-8 -1 -9.70323e-8 -1.81936e-8 -0.7071068 -0.7071068 -1.2129e-8 0 -1 3.03226e-8 0.7071066 -0.7071069 0 1 -3.21419e-7 -4.24516e-8 0.7071069 0.7071065 -1.2129e-8 6.32739e-8 0.7486157 -0.6630041 -0.5293513 0.5293513 -0.6630042 -0.7486158 -6.9026e-8 -0.6630042 -0.5293512 -0.5293514 -0.663004 2.30087e-8 -0.7486158 -0.6630041 0.5293512 -0.5293514 -0.6630041 0.7486158 -1.86945e-7 -0.6630042 0.5293514 0.5293511 -0.6630041 9.88537e-9 2.82439e-8 -1 + + + + + + + + + + 0.5 0.5 5.5 0.5000007 4.035534 4.035534 -2 3 4.035534 -3.035534 0.5000005 4.035534 -2 -2 4.035534 0.4999992 -3.035534 4.035534 2.999999 -2.000001 4.035534 4.035534 0.4999981 4.035534 3.000002 2.999998 4.035534 0.500001 5.5 0.4999998 -3.035533 4.035535 0.4999998 -4.5 0.5000008 0.4999998 -3.035534 -3.035533 0.4999998 0.4999989 -4.5 0.4999998 4.035532 -3.035535 0.4999998 5.5 0.4999973 0.4999998 4.035537 4.035532 0.4999998 0.5000007 4.035534 -3.035534 -2 3 -3.035534 -3.035534 0.5000005 -3.035534 -2 -2 -3.035534 0.4999992 -3.035534 -3.035534 2.999999 -2.000001 -3.035534 4.035534 0.4999981 -3.035534 3.000002 2.999998 -3.035534 0.5 0.5 -4.5 + + + + + + + + + + 0.954739 2.15833e-7 -9.43795e-9 0.8185035 -0.07019225 0.07925587 0.7049205 -0.02852993 0.5855967 0.6144843 1.61232e-7 0.6938309 0.7364077 -0.004867014 0.5840719 0.8185036 -0.07019225 -0.07925597 0.7049205 -0.02852995 -0.5855966 0.6144844 1.36677e-7 -0.6938309 0.7364076 -0.004866982 -0.584072 0.7071067 -5.4318e-8 -0.2357023 0.2724478 0.2724477 -0.03612882 -1.08617e-14 7.48297e-8 -5.34923e-9 0.2724474 -0.2724474 0.03612879 0.7071068 7.1471e-9 0.2357022 0.2724478 0.2724477 0.03612882 3.23075e-14 2.23624e-7 1.56345e-8 0.2724472 -0.2724473 -0.03612873 0.8185034 0.07019258 0.07925641 0.7364079 0.004867376 -0.5840717 0.6144844 1.60997e-7 -0.6938308 0.7049204 0.02853028 -0.5855968 0.8185035 0.07019259 -0.07925631 0.736408 0.004867405 0.5840716 0.6144844 1.54593e-7 0.6938308 0.7049204 0.02853028 0.5855969 0.954739 2.15833e-7 9.43795e-9 + + + + + + + + + + 2.26065e-7 -1 3.95415e-8 -0.1282771 -0.6575266 0.742431 -0.3587224 -0.8478319 0.3905115 1.75624e-7 -1 7.68394e-8 0.3255071 -0.8483869 -0.4174744 -0.1282772 -0.6575266 -0.742431 -0.3587225 -0.8478319 -0.3905116 -5.29431e-8 -1 -2.43878e-7 0.325507 -0.848387 0.4174741 0.3162278 -1.15066e-8 0.9486833 0.06601453 0.06601457 0.9956325 -2.5066e-8 0.07130344 0.9974547 0.06601458 -0.06601456 -0.9956325 0.3162277 -2.87665e-8 -0.9486833 0.06601455 0.06601453 -0.9956325 -1.99311e-8 0.06974413 -0.9975649 0.06601449 -0.06601451 0.9956325 -0.1282779 0.6575266 0.7424309 0.3255066 0.848387 0.4174746 -1.66844e-7 1 8.42766e-8 -0.3587228 0.8478319 -0.3905113 -0.1282778 0.6575265 -0.7424309 0.3255067 0.8483869 -0.4174747 2.93614e-8 1 -2.48815e-7 -0.3587227 0.847832 0.3905111 -2.26065e-7 1 2.82439e-8 + + + + + + + + + + + + + + + + + +

0 0 0 0 1 1 1 1 2 2 2 2 0 0 0 0 2 2 2 2 3 3 3 3 0 0 0 0 3 3 3 3 4 4 4 4 0 0 0 0 4 4 4 4 5 5 5 5 0 0 0 0 5 5 5 5 6 6 6 6 0 0 0 0 6 6 6 6 7 7 7 7 0 0 0 0 7 7 7 7 8 8 8 8 0 0 0 0 8 8 8 8 1 1 1 1 1 1 1 1 9 9 9 9 10 10 10 10 1 1 1 1 10 10 10 10 2 2 2 2 2 2 2 2 10 10 10 10 11 11 11 11 2 2 2 2 11 11 11 11 3 3 3 3 3 3 3 3 11 11 11 11 12 12 12 12 3 3 3 3 12 12 12 12 4 4 4 4 4 4 4 4 12 12 12 12 13 13 13 13 4 4 4 4 13 13 13 13 5 5 5 5 5 5 5 5 13 13 13 13 14 14 14 14 5 5 5 5 14 14 14 14 6 6 6 6 6 6 6 6 14 14 14 14 15 15 15 15 6 6 6 6 15 15 15 15 7 7 7 7 7 7 7 7 15 15 15 15 16 16 16 16 7 7 7 7 16 16 16 16 8 8 8 8 8 8 8 8 16 16 16 16 9 9 9 9 8 8 8 8 9 9 9 9 1 1 1 1 9 9 9 9 17 17 17 17 18 18 18 18 9 9 9 9 18 18 18 18 10 10 10 10 10 10 10 10 18 18 18 18 19 19 19 19 10 10 10 10 19 19 19 19 11 11 11 11 11 11 11 11 19 19 19 19 20 20 20 20 11 11 11 11 20 20 20 20 12 12 12 12 12 12 12 12 20 20 20 20 21 21 21 21 12 12 12 12 21 21 21 21 13 13 13 13 13 13 13 13 21 21 21 21 22 22 22 22 13 13 13 13 22 22 22 22 14 14 14 14 14 14 14 14 22 22 22 22 23 23 23 23 14 14 14 14 23 23 23 23 15 15 15 15 15 15 15 15 23 23 23 23 24 24 24 24 15 15 15 15 24 24 24 24 16 16 16 16 16 16 16 16 24 24 24 24 17 17 17 17 16 16 16 16 17 17 17 17 9 9 9 9 25 25 25 25 18 18 18 18 17 17 17 17 25 25 25 25 19 19 19 19 18 18 18 18 25 25 25 25 20 20 20 20 19 19 19 19 25 25 25 25 21 21 21 21 20 20 20 20 25 25 25 25 22 22 22 22 21 21 21 21 25 25 25 25 23 23 23 23 22 22 22 22 25 25 25 25 24 24 24 24 23 23 23 23 25 25 25 25 17 17 17 17 24 24 24 24

+
+
+
+ + + + 0 0 5 -1.89276e-7 4.330127 2.5 -3.75 2.165064 2.5 -3.75 -2.165064 2.5 5.16362e-8 -4.330127 2.5 3.75 -2.165064 2.5 3.750001 2.165062 2.5 -1.89276e-7 4.330127 -2.5 -3.75 2.165064 -2.5 -3.75 -2.165063 -2.5 5.16362e-8 -4.330127 -2.5 3.75 -2.165064 -2.5 3.750001 2.165062 -2.5 0 0 -5 + + + + + + + + + + -1.99952e-8 1.33301e-8 1 7.03834e-8 0.8973439 0.441332 -0.7771226 0.448672 0.441332 -0.7771226 -0.448672 0.441332 -3.51917e-8 -0.8973439 0.441332 0.7771224 -0.4486721 0.441332 0.7771227 0.4486718 0.441332 8.44601e-8 0.8973438 -0.4413321 -0.7771226 0.4486719 -0.4413322 -0.7771226 -0.4486719 -0.4413321 -4.92684e-8 -0.8973439 -0.4413321 0.7771225 -0.4486721 -0.4413321 0.7771227 0.4486717 -0.4413321 -6.66506e-9 1.33301e-8 -1 + + + + + + + + + + 0.9166666 1 0.999001 0.9999999 0.6666667 0.9990011 0.1666667 0.6666666 0.9990012 0.3333333 0.6666667 0.9990011 0.5 0.6666667 0.9990011 0.6666666 0.6666667 0.999001 0.8333333 0.6666667 0.999001 0.9999999 0.3333333 0.999001 0.1666666 0.3333333 0.9990011 0.3333333 0.3333333 0.9990011 0.5 0.3333333 0.999001 0.6666666 0.3333333 0.999001 0.8333332 0.3333333 0.9990009 0.9166666 0 0.999001 0.08333331 1 0.999001 -5.96046e-8 0.6666667 0.9990011 0.25 1 0.999001 0.4166666 1 0.999001 0.5833333 1 0.999001 0.7499999 1 0.999001 -5.96046e-8 0.6666667 0.9990011 -5.96046e-8 0.3333333 0.999001 -5.96046e-8 0.6666667 0.9990011 0.08333328 0 0.999001 -5.96046e-8 0.3333333 0.999001 0.25 0 0.999001 0.4166666 0 0.999001 0.5833333 0 0.999001 0.7499999 0 0.999001 + + + + + + + + + + -0.8660253 -0.5000002 -1.06513e-8 -0.8660252 -0.09738706 0.1980136 -0.4161448 -0.7597387 0.03960266 -3.09715e-7 -1 1.33301e-8 0.4498806 -0.7402613 0.03960252 0.8660254 -0.5 2.39814e-8 0.8660254 0.01947738 0.03960269 0.8660254 0.4999999 1.06513e-8 0.416145 0.7597387 0.03960266 3.30363e-7 1 -1.33301e-8 -0.4498804 0.7402614 0.03960264 -0.8660253 0.5000001 -2.39814e-8 -0.8660254 0.09738707 -0.1980133 -0.8660254 -0.09738693 0.1980133 -0.8660254 -0.09738697 -0.1980133 -0.4498806 -0.7402613 0.03960274 -0.8660254 -0.09738709 0.1980137 0.4161448 -0.7597387 0.03960259 0.8660254 -0.01947747 0.03960272 0.4498808 0.7402612 0.03960273 -0.4161446 0.7597387 0.0396026 -0.8660254 0.09738708 0.1980132 -0.8660254 -0.5 -8.92948e-10 -0.8660254 -0.09738697 -0.1980133 0 -1 -1.33301e-8 0.8660254 -0.5 -1.24372e-8 0.8660255 0.4999998 8.92944e-10 2.75302e-7 1 1.33301e-8 -0.8660254 0.5 1.24372e-8 + + + + + + + + + + -0.5000002 0.8660253 -2.15418e-8 -0.2469142 0.4276673 -0.8695597 -0.4071567 0.176304 -0.896181 -1 3.09715e-7 -1.99952e-8 -0.3562622 -0.2644559 -0.896181 -0.5 -0.8660254 1.54663e-9 0.05089461 -0.4407601 -0.896181 0.4999999 -0.8660254 2.15418e-8 0.4071567 -0.1763041 -0.8961809 1 -3.30363e-7 1.99952e-8 0.3562622 0.2644559 -0.896181 0.5000001 0.8660253 -1.54663e-9 0.2469139 0.4276673 -0.8695598 -0.2469138 0.4276673 -0.8695598 0.2469139 -0.4276674 -0.8695598 0.3562622 -0.2644562 -0.8961809 -0.2469142 0.4276672 -0.8695597 0.4071568 0.1763041 -0.8961809 0.05089469 0.4407602 -0.8961809 -0.3562621 0.2644562 -0.8961809 -0.4071568 -0.176304 -0.8961809 -0.2469138 -0.4276673 -0.8695598 0.5 -0.8660254 -1.48767e-8 0.2469139 -0.4276674 -0.8695598 1 0 -6.66506e-9 0.5 0.8660254 8.21169e-9 -0.4999998 0.8660255 1.48767e-8 -1 2.75302e-7 6.66506e-9 -0.5 -0.8660254 -8.21169e-9 + + + + + + + + + + + + + + + + + +

0 0 14 0 1 1 15 1 2 2 2 2 0 0 16 3 2 2 2 2 3 3 3 4 0 0 17 5 3 3 3 4 4 4 4 6 0 0 18 7 4 4 4 6 5 5 5 8 0 0 19 9 5 5 5 8 6 6 6 10 0 0 0 11 6 6 6 10 1 1 1 12 1 1 20 13 7 7 21 14 8 8 8 15 1 1 22 16 8 8 8 15 2 2 2 2 2 2 2 2 8 8 8 15 9 9 9 17 2 2 2 2 9 9 9 17 3 3 3 4 3 3 3 4 9 9 9 17 10 10 10 18 3 3 3 4 10 10 10 18 4 4 4 6 4 4 4 6 10 10 10 18 11 11 11 19 4 4 4 6 11 11 11 19 5 5 5 8 5 5 5 8 11 11 11 19 12 12 12 20 5 5 5 8 12 12 12 20 6 6 6 10 6 6 6 10 12 12 12 20 7 7 7 21 6 6 6 10 7 7 7 21 1 1 1 12 13 13 23 22 8 8 8 15 7 7 24 23 13 13 25 24 9 9 9 17 8 8 8 15 13 13 26 25 10 10 10 18 9 9 9 17 13 13 27 26 11 11 11 19 10 10 10 18 13 13 28 27 12 12 12 20 11 11 11 19 13 13 13 28 7 7 7 21 12 12 12 20

+
+
+
+
+ + + + + 0 0 0 + + + + + + + + + + + + + + + + + + + + 1 + 1 + 1 + 1 + + + + + + + + + + + + + + 1 + 1 + 1 + 1 + + + + + + + + + + + + + + 1 + 1 + 1 + 1 + + + + + + + + + + + + + + 1 + 1 + 1 + 1 + + + + + + + + +
\ No newline at end of file diff --git a/Templates/Full/game/art/environment/LightVolume_Sphere.cs b/Templates/Full/game/art/environment/LightVolume_Sphere.cs new file mode 100644 index 000000000..475da3bee --- /dev/null +++ b/Templates/Full/game/art/environment/LightVolume_Sphere.cs @@ -0,0 +1,8 @@ + +singleton TSShapeConstructor(LightVolume_SphereDAE) +{ + baseShape = "./LightVolume_Sphere.DAE"; + lodType = "TrailingNumber"; + neverImport = "env*"; + loadLights = "0"; +}; diff --git a/Templates/Full/game/art/environment/LightVolume_Sphere.dts b/Templates/Full/game/art/environment/LightVolume_Sphere.dts new file mode 100644 index 0000000000000000000000000000000000000000..223993dd637b8baf3e81194b0aa8598c58df0429 GIT binary patch literal 12883 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 + %vp.setShaderConst( "$strength", $VolFogGlowPostFx::glowStrength ); + %vp=%this-->hor; + %vp.setShaderConst( "$strength", $VolFogGlowPostFx::glowStrength ); +} \ No newline at end of file diff --git a/Templates/Full/game/core/scripts/client/postFx/postFxManager.gui.settings.cs b/Templates/Full/game/core/scripts/client/postFx/postFxManager.gui.settings.cs index d30d2314b..77d664f41 100644 --- a/Templates/Full/game/core/scripts/client/postFx/postFxManager.gui.settings.cs +++ b/Templates/Full/game/core/scripts/client/postFx/postFxManager.gui.settings.cs @@ -70,6 +70,7 @@ function PostFXManager::settingsSetEnabled(%this, %bEnablePostFX) postVerbose("% - PostFX Manager - PostFX disabled"); } + VolFogGlowPostFx.disable(); } function PostFXManager::settingsEffectSetEnabled(%this, %sName, %bEnable) diff --git a/Templates/Full/game/core/scripts/client/renderManager.cs b/Templates/Full/game/core/scripts/client/renderManager.cs index dcd1628fe..5734bbce6 100644 --- a/Templates/Full/game/core/scripts/client/renderManager.cs +++ b/Templates/Full/game/core/scripts/client/renderManager.cs @@ -75,6 +75,8 @@ function initRenderManager() DiffuseRenderPassManager.addManager( new RenderParticleMgr() { renderOrder = 1.35; processAddOrder = 1.35; } ); DiffuseRenderPassManager.addManager( new RenderTranslucentMgr() { renderOrder = 1.4; processAddOrder = 1.4; } ); + DiffuseRenderPassManager.addManager(new RenderObjectMgr(FogBin){ bintype = "ObjectVolumetricFog"; renderOrder = 1.45; processAddOrder = 1.45; } ); + // Note that the GlowPostFx is triggered after this bin. DiffuseRenderPassManager.addManager( new RenderGlowMgr(GlowBin) { renderOrder = 1.5; processAddOrder = 1.5; } ); diff --git a/Templates/Full/game/core/scripts/client/shaders.cs b/Templates/Full/game/core/scripts/client/shaders.cs index 98d0529eb..002053a1a 100644 --- a/Templates/Full/game/core/scripts/client/shaders.cs +++ b/Templates/Full/game/core/scripts/client/shaders.cs @@ -101,4 +101,40 @@ new ShaderData( fxFoliageReplicatorShader ) samplerNames[1] = "$alphaMap"; pixVersion = 1.4; +}; + +singleton ShaderData( VolumetricFogPrePassShader ) +{ + DXVertexShaderFile = "shaders/common/VolumetricFog/VFogPreV.hlsl"; + DXPixelShaderFile = "shaders/common/VolumetricFog/VFogPreP.hlsl"; + + OGLVertexShaderFile = "shaders/common/VolumetricFog/gl/VFogPreV.glsl"; + OGLPixelShaderFile = "shaders/common/VolumetricFog/gl/VFogPreP.glsl"; + + pixVersion = 3.0; +}; +singleton ShaderData( VolumetricFogShader ) +{ + DXVertexShaderFile = "shaders/common/VolumetricFog/VFogV.hlsl"; + DXPixelShaderFile = "shaders/common/VolumetricFog/VFogP.hlsl"; + + OGLVertexShaderFile = "shaders/common/VolumetricFog/gl/VFogV.glsl"; + OGLPixelShaderFile = "shaders/common/VolumetricFog/gl/VFogP.glsl"; + + samplerNames[0] = "$prepassTex"; + samplerNames[1] = "$depthBuffer"; + samplerNames[2] = "$frontBuffer"; + samplerNames[3] = "$density"; + + pixVersion = 3.0; +}; +singleton ShaderData( VolumetricFogReflectionShader ) +{ + DXVertexShaderFile = "shaders/common/VolumetricFog/VFogPreV.hlsl"; + DXPixelShaderFile = "shaders/common/VolumetricFog/VFogRefl.hlsl"; + + OGLVertexShaderFile = "shaders/common/VolumetricFog/gl/VFogPreV.glsl"; + OGLPixelShaderFile = "shaders/common/VolumetricFog/gl/VFogRefl.glsl"; + + pixVersion = 3.0; }; \ No newline at end of file diff --git a/Templates/Full/game/scripts/server/VolumetricFog.cs b/Templates/Full/game/scripts/server/VolumetricFog.cs new file mode 100644 index 000000000..53e03adf3 --- /dev/null +++ b/Templates/Full/game/scripts/server/VolumetricFog.cs @@ -0,0 +1,106 @@ +//----------------------------------------------------------------------------- +// Copyright (c) 2012 GarageGames, LLC +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. +//----------------------------------------------------------------------------- + +function VolumetricFog::onEnterFog(%this,%obj) +{ + // This method is called whenever the control object (Camera or Player) + // %obj enters the fog area. + + // echo("Control Object " @ %obj @ " enters fog " @ %this); +} + +function VolumetricFog::onLeaveFog(%this,%obj) +{ + // This method is called whenever the control object (Camera or Player) + // %obj leaves the fog area. + + // echo("Control Object " @ %obj @ " left fog " @ %this); +} + +function VolumetricFog::Dissolve(%this,%speed,%delete) +{ + // This method dissolves the fog at speed milliseconds + %this.isBuilding = true; + if (%this.FogDensity > 0) + { + %this.setFogDensity(%this.FogDensity - 0.005); + %this.schedule(%speed,Dissolve,%speed,%delete); + } + else + { + %this.isBuilding = false; + %this.SetFogDensity(0.0); + if (%delete !$= "" && %delete !$="0" && %delete !$="false") + %this.schedule(250,delete); + } +} + +function VolumetricFog::Thicken(%this,%speed, %end_density) +{ + // This method thickens the fog at speed milliseconds to a density of %end_density + + %this.isBuilding = true; + if (%this.FogDensity + 0.005 < %end_density) + { + %this.setFogDensity(%this.FogDensity + 0.005); + %this.schedule(%speed,Thicken,%speed, %end_density); + } + else + { + %this.setFogDensity(%end_density); + %this.isBuilding = false; + } +} + +function GenerateFog(%pos,%scale,%color,%density) +{ + // This function can be used to generate some fog caused by massive gunfire etc. + // Change shape and modulation data to your likings. + + %fog=new VolumetricFog() { + shapeName = "art/environment/Fog_Sphere.dts"; + fogColor = %color; + fogDensity = "0.0"; + ignoreWater = "0"; + MinSize = "250"; + FadeSize = "750"; + texture = "art/environment/FogMod_heavy.dds"; + tiles = "1"; + modStrength = "0.2"; + PrimSpeed = "-0.01 0.04"; + SecSpeed = "0.02 0.02"; + position = %pos; + rotation = "0 0 1 20.354"; + scale = %scale; + canSave = "1"; + canSaveDynamicFields = "1"; + }; + + if (isObject(%fog)) + { + MissionCleanup.add(%fog); + + %fog.Thicken(500,%density); + } + + return %fog; +} \ No newline at end of file diff --git a/Templates/Full/game/scripts/server/scriptExec.cs b/Templates/Full/game/scripts/server/scriptExec.cs index 64d4adc06..26f4f8280 100644 --- a/Templates/Full/game/scripts/server/scriptExec.cs +++ b/Templates/Full/game/scripts/server/scriptExec.cs @@ -24,6 +24,7 @@ // a server is constructed. exec("./camera.cs"); exec("./triggers.cs"); +exec("./VolumetricFog.cs"); exec("./inventory.cs"); exec("./shapeBase.cs"); exec("./item.cs"); diff --git a/Templates/Full/game/shaders/common/VolumetricFog/VFogP.hlsl b/Templates/Full/game/shaders/common/VolumetricFog/VFogP.hlsl new file mode 100644 index 000000000..aaadbf479 --- /dev/null +++ b/Templates/Full/game/shaders/common/VolumetricFog/VFogP.hlsl @@ -0,0 +1,87 @@ +//----------------------------------------------------------------------------- +// Copyright (c) 2012 GarageGames, LLC +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. +//----------------------------------------------------------------------------- + +// Volumetric Fog final pixel shader V2.00 + +#include "shadergen:/autogenConditioners.h" +#include "../torque.hlsl" + +uniform sampler2D prepassTex : register(S0); +uniform sampler2D depthBuffer : register(S1); +uniform sampler2D frontBuffer : register(S2); +uniform sampler2D density : register(S3); + +uniform float accumTime; +uniform float4 fogColor; +uniform float fogDensity; +uniform float preBias; +uniform float textured; +uniform float modstrength; +uniform float4 modspeed;//xy speed layer 1, zw speed layer 2 +uniform float2 viewpoint; +uniform float2 texscale; +uniform float3 ambientColor; +uniform float numtiles; +uniform float fadesize; +uniform float2 PixelSize; + +struct ConnectData +{ + float4 hpos : POSITION; + float4 htpos : TEXCOORD0; + float2 uv0 : TEXCOORD1; +}; + +float4 main( ConnectData IN ) : COLOR0 +{ + float2 uvscreen=((IN.htpos.xy/IN.htpos.w) + 1.0 ) / 2.0; + uvscreen.y = 1.0 - uvscreen.y; + + float obj_test = prepassUncondition( prepassTex, uvscreen).w * preBias; + float depth = tex2D(depthBuffer,uvscreen).r; + float front = tex2D(frontBuffer,uvscreen).r; + + if (depth <= front) + return float4(0,0,0,0); + else if ( obj_test < depth ) + depth = obj_test; + if ( front >= 0.0) + depth -= front; + + float diff = 1.0; + float3 col = fogColor.rgb; + if (textured != 0.0) + { + float2 offset = viewpoint + ((-0.5 + (texscale * uvscreen)) * numtiles); + + float2 mod1 = tex2D(density,(offset + (modspeed.xy*accumTime))).rg; + float2 mod2= tex2D(density,(offset + (modspeed.zw*accumTime))).rg; + diff = (mod2.r + mod1.r) * modstrength; + col *= (2.0 - ((mod1.g + mod2.g) * fadesize))/2.0; + } + + col *= ambientColor; + + float4 resultColor = float4(col, 1.0 - saturate(exp(-fogDensity * depth * diff * fadesize))); + + return hdrEncode(resultColor); +} diff --git a/Templates/Full/game/shaders/common/VolumetricFog/VFogPreP.hlsl b/Templates/Full/game/shaders/common/VolumetricFog/VFogPreP.hlsl new file mode 100644 index 000000000..bb06f5f7c --- /dev/null +++ b/Templates/Full/game/shaders/common/VolumetricFog/VFogPreP.hlsl @@ -0,0 +1,39 @@ +//----------------------------------------------------------------------------- +// Copyright (c) 2012 GarageGames, LLC +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. +//----------------------------------------------------------------------------- + +// Volumetric Fog prepass pixel shader V1.00 + +struct ConnectData +{ + float4 hpos : POSITION; + float4 pos : TEXCOORD0; +}; + +float4 main( ConnectData IN ) : COLOR0 +{ + float OUT; + + clip( IN.pos.w ); + OUT = IN.pos.w; + + return float4(OUT,0,0,1); +} diff --git a/Templates/Full/game/shaders/common/VolumetricFog/VFogPreV.hlsl b/Templates/Full/game/shaders/common/VolumetricFog/VFogPreV.hlsl new file mode 100644 index 000000000..2d13cdf01 --- /dev/null +++ b/Templates/Full/game/shaders/common/VolumetricFog/VFogPreV.hlsl @@ -0,0 +1,46 @@ +//----------------------------------------------------------------------------- +// Copyright (c) 2012 GarageGames, LLC +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. +//----------------------------------------------------------------------------- + +// Volumetric Fog prepass vertex shader V1.00 + +#include "shaders/common/hlslstructs.h" + +struct ConnectData +{ + float4 hpos : POSITION; + float4 pos : TEXCOORD0; +}; + +uniform float4x4 modelView; + +ConnectData main( VertexIn_P IN) +{ + ConnectData OUT; + + float4 inPos = IN.pos; + inPos.w = 1.0; + + OUT.hpos = mul( modelView, inPos ); + OUT.pos = OUT.hpos; + + return OUT; +} diff --git a/Templates/Full/game/shaders/common/VolumetricFog/VFogRefl.hlsl b/Templates/Full/game/shaders/common/VolumetricFog/VFogRefl.hlsl new file mode 100644 index 000000000..bd9866cf8 --- /dev/null +++ b/Templates/Full/game/shaders/common/VolumetricFog/VFogRefl.hlsl @@ -0,0 +1,37 @@ +//----------------------------------------------------------------------------- +// Copyright (c) 2012 GarageGames, LLC +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. +//----------------------------------------------------------------------------- + +// Volumetric Fog Reflection pixel shader V1.00 +uniform float4 fogColor; +uniform float fogDensity; +uniform float reflStrength; + +struct ConnectData +{ + float4 hpos : POSITION; + float4 pos : TEXCOORD0; +}; + +float4 main( ConnectData IN ) : COLOR0 +{ + return float4(fogColor.rgb,saturate(fogDensity*reflStrength)); +} diff --git a/Templates/Full/game/shaders/common/VolumetricFog/VFogV.hlsl b/Templates/Full/game/shaders/common/VolumetricFog/VFogV.hlsl new file mode 100644 index 000000000..7f86802b5 --- /dev/null +++ b/Templates/Full/game/shaders/common/VolumetricFog/VFogV.hlsl @@ -0,0 +1,45 @@ +//----------------------------------------------------------------------------- +// Copyright (c) 2012 GarageGames, LLC +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. +//----------------------------------------------------------------------------- + +// Volumetric Fog final vertex shader V1.00 + +#include "shaders/common/hlslstructs.h" + +struct ConnectData +{ + float4 hpos : POSITION; + float4 htpos : TEXCOORD0; + float2 uv0 : TEXCOORD1; +}; + +uniform float4x4 modelView; + +ConnectData main( VertexIn_PNT IN) +{ + ConnectData OUT; + + OUT.hpos = mul(modelView, IN.pos); + OUT.htpos = OUT.hpos; + OUT.uv0 = IN.uv0; + + return OUT; +} diff --git a/Templates/Full/game/shaders/common/VolumetricFog/gl/VFogP.glsl b/Templates/Full/game/shaders/common/VolumetricFog/gl/VFogP.glsl new file mode 100644 index 000000000..7895d9e2d --- /dev/null +++ b/Templates/Full/game/shaders/common/VolumetricFog/gl/VFogP.glsl @@ -0,0 +1,87 @@ +//----------------------------------------------------------------------------- +// Copyright (c) 2012 GarageGames, LLC +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. +//----------------------------------------------------------------------------- + +#include "../../gl/hlslCompat.glsl" +#include "shadergen:/autogenConditioners.h" +#include "../../gl/torque.glsl" + +uniform sampler2D prepassTex; +uniform sampler2D depthBuffer; +uniform sampler2D frontBuffer; +uniform sampler2D density; + +uniform float accumTime; +uniform vec4 fogColor; +uniform float fogDensity; +uniform float preBias; +uniform float textured; +uniform float modstrength; +uniform vec4 modspeed;//xy speed layer 1, zw speed layer 2 +uniform vec2 viewpoint; +uniform vec2 texscale; +uniform vec3 ambientColor; +uniform float numtiles; +uniform float fadesize; +uniform vec2 PixelSize; + +in vec4 _hpos; +#define IN_hpos _hpos +out vec4 OUT_col; + +void main() +{ + vec2 uvscreen=((IN_hpos.xy/IN_hpos.w) + 1.0 ) / 2.0; + uvscreen.y = 1.0 - uvscreen.y; + + float obj_test = prepassUncondition( prepassTex, uvscreen).w * preBias; + float depth = tex2D(depthBuffer,uvscreen).r; + float front = tex2D(frontBuffer,uvscreen).r; + + if (depth <= front) + { + OUT_col = vec4(0,0,0,0); + return; + } + + else if ( obj_test < depth ) + depth = obj_test; + if ( front >= 0.0) + depth -= front; + + float diff = 1.0; + vec3 col = fogColor.rgb; + if (textured != 0.0) + { + vec2 offset = viewpoint + ((-0.5 + (texscale * uvscreen)) * numtiles); + + vec2 mod1 = tex2D(density,(offset + (modspeed.xy*accumTime))).rg; + vec2 mod2= tex2D(density,(offset + (modspeed.zw*accumTime))).rg; + diff = (mod2.r + mod1.r) * modstrength; + col *= (2.0 - ((mod1.g + mod2.g) * fadesize))/2.0; + } + + col *= ambientColor; + + vec4 returnColor = vec4(col, 1.0 - saturate(exp(-fogDensity * depth * diff * fadesize))); + + OUT_col = hdrEncode(returnColor); +} diff --git a/Templates/Full/game/shaders/common/VolumetricFog/gl/VFogPreP.glsl b/Templates/Full/game/shaders/common/VolumetricFog/gl/VFogPreP.glsl new file mode 100644 index 000000000..017ea6ef8 --- /dev/null +++ b/Templates/Full/game/shaders/common/VolumetricFog/gl/VFogPreP.glsl @@ -0,0 +1,37 @@ +//----------------------------------------------------------------------------- +// Copyright (c) 2012 GarageGames, LLC +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. +//----------------------------------------------------------------------------- + +#include "../../gl/hlslCompat.glsl" + +in vec4 _hpos; +#define IN_hpos _hpos + +out vec4 OUT_col; + +void main() +{ + float OUT; + clip( IN_hpos.w ); + OUT = IN_hpos.w; + + OUT_col = vec4(OUT,0,0,1); +} diff --git a/Templates/Full/game/shaders/common/VolumetricFog/gl/VFogPreV.glsl b/Templates/Full/game/shaders/common/VolumetricFog/gl/VFogPreV.glsl new file mode 100644 index 000000000..2f2a1318a --- /dev/null +++ b/Templates/Full/game/shaders/common/VolumetricFog/gl/VFogPreV.glsl @@ -0,0 +1,42 @@ +//----------------------------------------------------------------------------- +// Copyright (c) 2012 GarageGames, LLC +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. +//----------------------------------------------------------------------------- + +#include "../../gl/hlslCompat.glsl" + +in vec4 vPosition; +#define IN_position vPosition + +out vec4 _hpos; +#define OUT_hpos _hpos + +uniform mat4 modelView; + +void main() +{ + vec4 inPos = IN_position; + inPos.w = 1.0; + + OUT_hpos = tMul( modelView, inPos ); + + gl_Position = OUT_hpos; + correctSSP(gl_Position); +} diff --git a/Templates/Full/game/shaders/common/VolumetricFog/gl/VFogRefl.glsl b/Templates/Full/game/shaders/common/VolumetricFog/gl/VFogRefl.glsl new file mode 100644 index 000000000..78e149fbf --- /dev/null +++ b/Templates/Full/game/shaders/common/VolumetricFog/gl/VFogRefl.glsl @@ -0,0 +1,33 @@ +//----------------------------------------------------------------------------- +// Copyright (c) 2012 GarageGames, LLC +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. +//----------------------------------------------------------------------------- + +#include "../../gl/hlslCompat.glsl" + +uniform vec4 fogColor; +uniform float fogDensity; +uniform float reflStrength; +out vec4 OUT_col; + +void main() +{ + OUT_col = vec4(fogColor.rgb,saturate(fogDensity*reflStrength)); +} diff --git a/Templates/Full/game/shaders/common/VolumetricFog/gl/VFogV.glsl b/Templates/Full/game/shaders/common/VolumetricFog/gl/VFogV.glsl new file mode 100644 index 000000000..57b3ba87e --- /dev/null +++ b/Templates/Full/game/shaders/common/VolumetricFog/gl/VFogV.glsl @@ -0,0 +1,38 @@ +//----------------------------------------------------------------------------- +// Copyright (c) 2012 GarageGames, LLC +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. +//----------------------------------------------------------------------------- + +#include "../../gl/hlslCompat.glsl" + +in vec4 vPosition; +#define IN_position vPosition + +out vec4 _hpos; +#define OUT_hpos _hpos + +uniform mat4 modelView; + +void main() +{ + OUT_hpos = tMul(modelView, IN_position); + gl_Position = OUT_hpos; + correctSSP(gl_Position); +} diff --git a/Templates/Full/game/shaders/common/postFx/VolFogGlowP.hlsl b/Templates/Full/game/shaders/common/postFx/VolFogGlowP.hlsl new file mode 100644 index 000000000..8a61b5928 --- /dev/null +++ b/Templates/Full/game/shaders/common/postFx/VolFogGlowP.hlsl @@ -0,0 +1,74 @@ +//----------------------------------------------------------------------------- +// Copyright (c) 2014 R.G.S. - Richards Game Studio, the Netherlands +// http://www.richardsgamestudio.com/ +// +// If you find this code useful or you are feeling particularly generous I +// would ask that you please go to http://www.richardsgamestudio.com/ then +// choose Donations from the menu on the left side and make a donation to +// Richards Game Studio. It will be highly appreciated. +// +// The MIT License: +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. +//----------------------------------------------------------------------------- + +// Volumetric Fog Glow postFx pixel shader V1.00 + +#include "./postFx.hlsl" + +uniform sampler2D diffuseMap : register(S0); +uniform float strength; + +struct VertToPix +{ + float4 hpos : POSITION; + + float2 uv0 : TEXCOORD0; + float2 uv1 : TEXCOORD1; + float2 uv2 : TEXCOORD2; + float2 uv3 : TEXCOORD3; + + float2 uv4 : TEXCOORD4; + float2 uv5 : TEXCOORD5; + float2 uv6 : TEXCOORD6; + float2 uv7 : TEXCOORD7; +}; + +float4 main( VertToPix IN ) : COLOR +{ + float4 kernel = float4( 0.175, 0.275, 0.375, 0.475 ) * strength; + + float4 OUT = 0; + OUT += tex2D( diffuseMap, IN.uv0 ) * kernel.x; + OUT += tex2D( diffuseMap, IN.uv1 ) * kernel.y; + OUT += tex2D( diffuseMap, IN.uv2 ) * kernel.z; + OUT += tex2D( diffuseMap, IN.uv3 ) * kernel.w; + + OUT += tex2D( diffuseMap, IN.uv4 ) * kernel.x; + OUT += tex2D( diffuseMap, IN.uv5 ) * kernel.y; + OUT += tex2D( diffuseMap, IN.uv6 ) * kernel.z; + OUT += tex2D( diffuseMap, IN.uv7 ) * kernel.w; + + // Calculate a lumenance value in the alpha so we + // can use alpha test to save fillrate. + float3 rgb2lum = float3( 0.30, 0.59, 0.11 ); + OUT.a = dot( OUT.rgb, rgb2lum ); + + return OUT; +} diff --git a/Templates/Full/game/shaders/common/postFx/gl/VolFogGlowP.glsl b/Templates/Full/game/shaders/common/postFx/gl/VolFogGlowP.glsl new file mode 100644 index 000000000..01b072dd9 --- /dev/null +++ b/Templates/Full/game/shaders/common/postFx/gl/VolFogGlowP.glsl @@ -0,0 +1,67 @@ +//----------------------------------------------------------------------------- +// Copyright (c) 2014 R.G.S. - Richards Game Studio, the Netherlands +// http://www.richardsgamestudio.com/ +// +// If you find this code useful or you are feeling particularly generous I +// would ask that you please go to http://www.richardsgamestudio.com/ then +// choose Donations from the menu on the left side and make a donation to +// Richards Game Studio. It will be highly appreciated. +// +// The MIT License: +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. +//----------------------------------------------------------------------------- + +// Volumetric Fog Glow postFx pixel shader V1.00 + +uniform sampler2D diffuseMap; +uniform float strength; + +out vec4 OUT_col; + +in vec2 uv0; +in vec2 uv1; +in vec2 uv2; +in vec2 uv3; + +in vec2 uv4; +in vec2 uv5; +in vec2 uv6; +in vec2 uv7; + +void main() +{ + vec4 kernel = vec4( 0.175, 0.275, 0.375, 0.475 ) * strength; + + OUT_col = vec4(0); + OUT_col += texture( diffuseMap, uv0 ) * kernel.x; + OUT_col += texture( diffuseMap, uv1 ) * kernel.y; + OUT_col += texture( diffuseMap, uv2 ) * kernel.z; + OUT_col += texture( diffuseMap, uv3 ) * kernel.w; + + OUT_col += texture( diffuseMap, uv4 ) * kernel.x; + OUT_col += texture( diffuseMap, uv5 ) * kernel.y; + OUT_col += texture( diffuseMap, uv6 ) * kernel.z; + OUT_col += texture( diffuseMap, uv7 ) * kernel.w; + + // Calculate a lumenance value in the alpha so we + // can use alpha test to save fillrate. + vec3 rgb2lum = vec3( 0.30, 0.59, 0.11 ); + OUT_col.a = dot( OUT_col.rgb, rgb2lum ); +} diff --git a/Templates/Full/game/tools/classIcons/VolumetricFog.png b/Templates/Full/game/tools/classIcons/VolumetricFog.png new file mode 100644 index 0000000000000000000000000000000000000000..5dc516cb53d99e0ae0d125507d6d95c9a2942107 GIT binary patch literal 3642 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 Date: Tue, 1 Dec 2015 00:49:37 -0600 Subject: [PATCH 051/109] Missed a few files. --- Engine/source/environment/VolumetricFog.cpp | 1319 +++++++++++++++++ Engine/source/environment/VolumetricFog.h | 243 +++ .../environment/VolumetricFogRTManager.cpp | 299 ++++ .../environment/VolumetricFogRTManager.h | 92 ++ 4 files changed, 1953 insertions(+) create mode 100644 Engine/source/environment/VolumetricFog.cpp create mode 100644 Engine/source/environment/VolumetricFog.h create mode 100644 Engine/source/environment/VolumetricFogRTManager.cpp create mode 100644 Engine/source/environment/VolumetricFogRTManager.h diff --git a/Engine/source/environment/VolumetricFog.cpp b/Engine/source/environment/VolumetricFog.cpp new file mode 100644 index 000000000..e6be112dc --- /dev/null +++ b/Engine/source/environment/VolumetricFog.cpp @@ -0,0 +1,1319 @@ +//----------------------------------------------------------------------------- +// Copyright (c) 2012 GarageGames, LLC +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. +//----------------------------------------------------------------------------- + +#include "environment/VolumetricFog.h" +#include "windowManager/platformWindowMgr.h" +#include "gfx/gfxTransformSaver.h" +#include "renderInstance/renderPassManager.h" +#include "math/mathIO.h" +#include "materials/shaderData.h" +#include "math/util/matrixSet.h" +#include "core/resourceManager.h" +#include "core/stream/bitStream.h" +#include "T3D/gameBase/gameConnection.h" +#include "T3D/shapeBase.h" +#include "ts/tsShapeInstance.h" +#include "console/engineAPI.h" +#include "gui/core/guiCanvas.h" +#include "VolumetricFogRTManager.h" +#include "lighting/lightInfo.h" +#include "lighting/lightManager.h" + +#define COLBOX_SCALE Point3F(1.02f, 1.02f, 1.02f) + +IMPLEMENT_CO_NETOBJECT_V1(VolumetricFog); + +ConsoleDocClass(VolumetricFog, +"@brief Volumetric Fog Object class. Main class defining the Volumetric\n" +"Fog objects in the scene. Used in conjunction with the VolumetricFogRTManager\n" +"class which is responsible for the required rendertargets.\n\n" +"Methods (exposed to script):\n" +" setFogColorF(color) Changes the overall fog color (color.rgba range 0.0 - 1.0).\n;" +" setFogColor(color) Changes the overall fog color color.rgba range 0 - 255).\n;" +" setFogDensity(density) Changes the overall fog density.\n" +" setFogModulation(strength, speed1, speed2) changes the strength\n" +" and the speeds of the 2 animation layers.\n\n" +"Callbacks:\n" +"onEnterFog triggered whenever the controlobject (Player or Camera) enters the Fog.\n" +" (current Fog object and the controlobject are exposed to script.\n" +"onLeaveFog triggered whenever the controlobject (Player or Camera) left the Fog.\n" +" (current Fog object and the controlobject are exposed to script.\n\n" +"@tsexample\n" +" new VolumetricFog()\n" +" {\n" +" shapeName = \"art/environment/FogRCube.dts\";\n" +" fogColor = \"200 200 200 128\";\n" +" fogDensity = \"0.2\";\n" +" ignoreWater = \"0\";\n" +" MinSize = \"250\";\n" +" FadeSize = \"750\";\n" +" texture = \"art/environment/FogMod_heavy.dds\";\n" +" tiles = \"1.5\";\n" +" modStrength = \"0.2\";\n" +" PrimSpeed = \"-0.01 0.04\";\n" +" SecSpeed = \"0.02 -0.02\";\n" +" position = \"748.644 656.371 65.3506\"; \n" +" rotation = \"0 0 1 20.354\";\n" +" scale = \"40 30 6\";\n" +" };\n" +"@endtsexample\n" +); + +IMPLEMENT_CALLBACK(VolumetricFog, onEnterFog, void, (SimObjectId obj), (obj), +"@brief Called when an object enters the volume of the Fog instance.\n\n" + +"@param obj the controlobject entering the fog."); + +IMPLEMENT_CALLBACK(VolumetricFog, onLeaveFog, void, (SimObjectId obj), (obj), +"@brief Called when an object left the volume of the Fog instance.\n\n" + +"@param obj the controlobject leaving the fog."); + + +VolumetricFog::VolumetricFog() +{ + AssertFatal(VFRTM != NULL, "VolumetricFog Fatal Error: No Manager found"); + + if (!VFRTM->IsInitialized()) + VFRTM->Init(); + + mNetFlags.set(Ghostable | ScopeAlways); + + mTypeMask |= EnvironmentObjectType | StaticObjectType; + + mPrepassTarget = NULL; + mDepthBufferTarget = NULL; + mFrontBufferTarget = NULL; + + z_buf = NULL; + mTexture = NULL; + + mIsVBDirty = false; + mIsPBDirty = false; + + mFogColor.set(200, 200, 200, 255); + mFogDensity = 0.3f; + mIgnoreWater = false; + mReflect = false; + mCamInFog = false; + mResizing = false; + mFogReflStrength = 20.0; + mUseGlow = false; + mGlowStrength = 0.3f; + mGlowing = 0; + mModifLightRays = false; + mLightRayMod = 1.0f; + mOldLightRayStrength = 0.1f; + + mShapeName = ""; + mShapeLoaded = false; + mMinDisplaySize = 10.0f; + mFadeSize = 0.0f; + mCurDetailLevel = 0; + mNumDetailLevels = 0; + det_size.clear(); + + mTextureName = ""; + mIsTextured = false; + mStrength = 0.5f; + mTexTiles = 1.0f; + mSpeed1.set(0.5f, 0.0f); + mSpeed2.set(0.1f, 0.1f); +} + +VolumetricFog::~VolumetricFog() +{ + if (isClientObject()) + return; + + for (S32 i = 0; i < det_size.size(); i++) + { + if (det_size[i].indices != NULL) + delete(det_size[i].indices); + if (det_size[i].piArray != NULL) + delete(det_size[i].piArray); + if (det_size[i].verts != NULL) + delete(det_size[i].verts); + } + det_size.clear(); + + if (z_buf.isValid()) + SAFE_DELETE(z_buf); + + if (!mTexture.isNull()) + mTexture.free(); +} + +void VolumetricFog::initPersistFields() +{ + addGroup("VolumetricFogData"); + addField("shapeName", TypeShapeFilename, Offset(mShapeName, VolumetricFog), + "Path and filename of the model file (.DTS, .DAE) to use for this Volume."); + addField("FogColor", TypeColorI, Offset(mFogColor, VolumetricFog), + "Fog color RGBA (Alpha is ignored)"); + addField("FogDensity", TypeF32, Offset(mFogDensity, VolumetricFog), + "Overal fog density value (0 disables the fog)."); + addField("IgnoreWater", TypeBool, Offset(mIgnoreWater, VolumetricFog), + "Set to true if volumetric fog should continue while submerged."); + addField("MinSize", TypeF32, Offset(mMinDisplaySize, VolumetricFog), + "Min size (in pixels) for fog to be rendered."); + addField("FadeSize", TypeF32, Offset(mFadeSize, VolumetricFog), + "Object size in pixels at which the FX-fading kicks in (0 disables fading)."); + endGroup("VolumetricFogData"); + + addGroup("VolumetricFogModulation"); + addField("texture", TypeImageFilename, Offset(mTextureName, VolumetricFog), + "A texture which contains Fogdensity modulator in the red channel and color with 1-green channel. No texture disables modulation."); + addField("tiles", TypeF32, Offset(mTexTiles, VolumetricFog), + "How many times the texture is mapped to the object."); + addField("modStrength", TypeF32, Offset(mStrength, VolumetricFog), + "Overall strength of the density modulation (0 disables modulation)."); + addField("PrimSpeed", TypePoint2F, Offset(mSpeed1, VolumetricFog), + "Overall primary speed of the density modulation (x-speed(u) y-speed(v))"); + addField("SecSpeed", TypePoint2F, Offset(mSpeed2, VolumetricFog), + "Overall secundary speed of the density modulation (x-speed(u) y-speed(v))"); + endGroup("VolumetricFogModulation"); + + addGroup("Reflections"); + addField("Reflectable", TypeBool, Offset(mReflect, VolumetricFog), + "Set to true if volumetric fog should be reflected."); + addField("ReflectStrength", TypeF32, Offset(mFogReflStrength, VolumetricFog), + "Strength of the reflections (0 disables the fog)."); + endGroup("Reflections"); + + addGroup("PostFX"); + addField("useGlow", TypeBool, Offset(mUseGlow, VolumetricFog), + "Set to true if volumetric fog should use glow PostFX."); + addField("glowStrength", TypeF32, Offset(mGlowStrength, VolumetricFog), + "Overall strength of the glow PostFX."); + addField("modLightRay", TypeBool, Offset(mModifLightRays, VolumetricFog), + "Set to true if volumetric fog should modify the brightness of the Lightrays."); + addField("lightRayMod", TypeF32, Offset(mLightRayMod, VolumetricFog), + "Modifier for LightRay PostFX when inside Fog."); + endGroup("PostFX"); + Parent::initPersistFields(); +} + +void VolumetricFog::inspectPostApply() +{ + Parent::inspectPostApply(); + mSpeed.set(mSpeed1.x, mSpeed1.y, mSpeed2.x, mSpeed2.y); + setMaskBits(VolumetricFogMask | FogColorMask | FogDensityMask | FogModulationMask | FogPostFXMask | FogShapeMask); +} + +bool VolumetricFog::onAdd() +{ + if (!Parent::onAdd()) + return false; + + if (!VFRTM->IsInitialized()) + { + Con::errorf("No VolumetricFogRTManager present!!"); + return false; + } + + resetWorldBox(); + + mShapeLoaded = LoadShape(); + + setRenderTransform(mObjToWorld); + + addToScene(); + ColBox.set(getTransform(), (mObjBox.getExtents() * getScale() * COLBOX_SCALE)); + mObjSize = mWorldBox.getGreatestDiagonalLength(); + mObjScale = getScale(); + mTexTiles = mAbs(mTexTiles); + mSpeed.set(mSpeed1.x, mSpeed1.y, mSpeed2.x, mSpeed2.y); + mInvScale = (1.0f / getMax(getMax(mObjScale.x, mObjScale.y), mObjScale.z)); + + if (isClientObject()) + { + conn = GameConnection::getConnectionToServer(); + if (!conn) + { + Con::errorf("VolumetricFog::onAdd - No Serverconnection"); + return false; + } + + glowFX = static_cast(Sim::findObject("VolFogGlowPostFx")); + + mOldLightRayStrength = Con::getFloatVariable("$LightRayPostFX::brightScalar",1.0f); + + GuiCanvas* cv = dynamic_cast(Sim::findObject("Canvas")); + if (cv == NULL) + { + Con::errorf("VolumetricFog::onAdd - Canvas not found!!"); + return false; + } + mPlatformWindow = cv->getPlatformWindow(); + VolumetricFogRTManager::getVolumetricFogRTMResizeSignal().notify(this, &VolumetricFog::handleResize); + GuiCanvas::getCanvasSizeChangeSignal().notify(this, &VolumetricFog::handleCanvasResize); + + InitTexture(); + return setupRenderer(); + } + + VFRTM->IncFogObjects(); + + return true; +} + +void VolumetricFog::onRemove() +{ + if (isClientObject()) + { + if (isTicking()) + { + setProcessTick(false); + if (mGlowing != 0) + { + mGlowing = 0; + glowFX->disable(); + } + _leaveFog(static_cast(conn->getControlObject())); + } + VolumetricFogRTManager::getVolumetricFogRTMResizeSignal().remove(this, &VolumetricFog::handleResize); + GuiCanvas::getCanvasSizeChangeSignal().remove(this, &VolumetricFog::handleCanvasResize); + } + removeFromScene(); + VFRTM->DecFogObjects(); + Parent::onRemove(); +} +void VolumetricFog::handleCanvasResize(GuiCanvas* canvas) +{ + UpdateBuffers(0,true); +} + +void VolumetricFog::handleResize(VolumetricFogRTManager *RTM, bool resize) +{ + if (resize) + { + mResizing = true; + RTM->FogAnswered(); + } + else + mResizing = false; + + if (mIsTextured) + { + F32 width = (F32)mPlatformWindow->getClientExtent().x; + F32 height = (F32)mPlatformWindow->getClientExtent().y; + if (!mPlatformWindow->isFullscreen()) + height -= 20;//subtract caption bar from rendertarget size. + mTexScale.x = 2.0f - ((F32)mTexture.getWidth() / width); + mTexScale.y = 2.0f - ((F32)mTexture.getHeight() / height); + } + + UpdateBuffers(0,true); +} + +//----------------------------------------------------------------------------- +// Loadshape extracted from TSMesh and TSShapeInstance +//----------------------------------------------------------------------------- + +bool VolumetricFog::LoadShape() +{ + GFXPrimitiveType GFXdrawTypes[] = { GFXTriangleList, GFXTriangleStrip }; + if (!mShapeName || mShapeName[0] == '\0') + { + Con::errorf("VolumetricFog::LoadShape() - No shape name! Volumetric Fog will not be rendered!"); + return false; + } + + // Load shape, server side only reads bounds and radius + + Resource mShape; + mShape = ResourceManager::get().load(mShapeName); + if (bool(mShape) == false) + { + Con::errorf("VolumetricFog::LoadShape() - Unable to load shape: %s", mShapeName); + return false; + } + + mObjBox = mShape->bounds; + mRadius = mShape->radius; + resetWorldBox(); + + if (!isClientObject()) + return false; + + TSShapeInstance *mShapeInstance = new TSShapeInstance(mShape, false); + meshes mesh_detail; + + for (S32 i = 0; i < det_size.size(); i++) + { + if (det_size[i].indices != NULL) + delete(det_size[i].indices); + if (det_size[i].piArray != NULL) + delete(det_size[i].piArray); + if (det_size[i].verts != NULL) + delete(det_size[i].verts); + } + det_size.clear(); + + // browsing model for detail levels + + for (U32 i = 0; i < mShape->details.size(); i++) + { + const TSDetail *detail = &mShape->details[i]; + mesh_detail.det_size = detail->size; + mesh_detail.sub_shape = detail->subShapeNum; + mesh_detail.obj_det = detail->objectDetailNum; + mesh_detail.verts = NULL; + mesh_detail.piArray = NULL; + mesh_detail.indices = NULL; + if (detail->size >= 0.0f && detail->subShapeNum >= 0) + det_size.push_back(mesh_detail); + } + + for (U32 i = 0; i < det_size.size(); i++) + { + const S32 ss = det_size[i].sub_shape; + if (ss >= 0) + { + const S32 start = mShape->subShapeFirstObject[ss]; + const S32 end = start + mShape->subShapeNumObjects[ss]; + for (S32 j = start; j < end; j++) + { + // Loading shape, only the first mesh for each detail will be used! + TSShapeInstance::MeshObjectInstance *meshObj = &mShapeInstance->mMeshObjects[j]; + if (!meshObj) + continue; + TSMesh *mesh = meshObj->getMesh(det_size[i].obj_det); + if (mesh != NULL) + { + const U32 numNrms = mesh->mNumVerts; + GFXVertexPNTT *tmpVerts = NULL; + tmpVerts = new GFXVertexPNTT[numNrms]; + mIsVBDirty = true; + for (U32 k = 0; k < numNrms; k++) + { + Point3F norm = mesh->mVertexData[k].normal(); + Point3F vert = mesh->mVertexData[k].vert(); + Point2F uv = mesh->mVertexData[k].tvert(); + tmpVerts[k].point = vert; + tmpVerts[k].texCoord = uv; + tmpVerts[k].normal = norm; + } + det_size[i].verts = tmpVerts; + det_size[i].num_verts = numNrms; + + det_size[i].piArray = new Vector(); + GFXPrimitive pInfo; + + det_size[i].indices = new Vector(); + + for (U32 k = 0; k < mesh->indices.size(); k++) + det_size[i].indices->push_back(mesh->indices[k]); + + U32 primitivesSize = mesh->primitives.size(); + for (U32 k = 0; k < primitivesSize; k++) + { + const TSDrawPrimitive & draw = mesh->primitives[k]; + GFXPrimitiveType drawType = GFXdrawTypes[draw.matIndex >> 30]; + switch (drawType) + { + case GFXTriangleList: + pInfo.type = drawType; + pInfo.numPrimitives = draw.numElements / 3; + pInfo.startIndex = draw.start; + // Use the first index to determine which 16-bit address space we are operating in + pInfo.startVertex = mesh->indices[draw.start] & 0xFFFF0000; + pInfo.minIndex = pInfo.startVertex; + pInfo.numVertices = getMin((U32)0x10000, mesh->mNumVerts - pInfo.startVertex); + break; + case GFXTriangleStrip: + pInfo.type = drawType; + pInfo.numPrimitives = draw.numElements - 2; + pInfo.startIndex = draw.start; + // Use the first index to determine which 16-bit address space we are operating in + pInfo.startVertex = mesh->indices[draw.start] & 0xFFFF0000; + pInfo.minIndex = pInfo.startVertex; + pInfo.numVertices = getMin((U32)0x10000, mesh->mNumVerts - pInfo.startVertex); + break; + default: + Con::errorf("VolumetricFog::LoadShape Unknown drawtype!?!"); + return false; + break; + } + det_size[i].piArray->push_back(pInfo); + j = end; + } + } + else + { + Con::errorf("VolumetricFog::LoadShape Error loading mesh from shape!"); + delete mShapeInstance; + return false; + } + mIsVBDirty = true; + mIsPBDirty = true; + } + } + } + + mNumDetailLevels = det_size.size(); + mCurDetailLevel = 0; + UpdateBuffers(mCurDetailLevel); + delete mShapeInstance; + + return true; +} + +//----------------------------------------------------------------------------- +// UpdateBuffers called whenever detaillevel changes (LOD) +//----------------------------------------------------------------------------- + + +bool VolumetricFog::UpdateBuffers(U32 dl, bool force) +{ + if (mVB.isNull() || mIsVBDirty || dl != mCurDetailLevel || force) + { + mVB.set(GFX, det_size[dl].num_verts, GFXBufferTypeDynamic); + mIsVBDirty = false; + } + GFXVertexPNTT *vertPtr = mVB.lock(); + if (!vertPtr) + { + mVB.unlock(); + return false; + } + dMemcpy(vertPtr, det_size[dl].verts, sizeof (GFXVertexPNTT)* det_size[dl].num_verts); + mVB.unlock(); + + if (mIsPBDirty || mPB.isNull() || dl != mCurDetailLevel || force) + { + #ifdef TORQUE_DEBUG + mPB.set(GFX, det_size[dl].indices->size(), det_size[dl].piArray->size(), GFXBufferTypeDynamic, avar("%s() - VolFogPrimBuffer (line %d)", __FUNCTION__, __LINE__)); + #else + mPB.set(GFX, det_size[dl].indices->size(), det_size[dl].piArray->size(), GFXBufferTypeDynamic); + #endif + U16 *ibIndices = NULL; + GFXPrimitive *piInput = NULL; + mPB.lock(&ibIndices, &piInput); + dCopyArray(ibIndices, det_size[dl].indices->address(), det_size[dl].indices->size()); + dMemcpy(piInput, det_size[dl].piArray->address(), det_size[dl].piArray->size() * sizeof(GFXPrimitive)); + mPB.unlock(); + mIsPBDirty = false; + } + mCurDetailLevel = dl; + return true; +} + +U32 VolumetricFog::packUpdate(NetConnection *con, U32 mask, BitStream *stream) +{ + U32 retMask = Parent::packUpdate(con, mask, stream); + if (stream->writeFlag(mask & FogColorMask)) + stream->write(mFogColor); + if (stream->writeFlag(mask & FogDensityMask)) + stream->write(mFogDensity); + if (stream->writeFlag(mask & FogModulationMask)) + { + stream->write(mTextureName); + mTexTiles = mFabs(mTexTiles); + stream->write(mTexTiles); + stream->write(mStrength); + mathWrite(*stream, mSpeed); + } + if (stream->writeFlag(mask & FogPostFXMask)) + { + stream->writeFlag(mUseGlow); + stream->write(mGlowStrength); + stream->writeFlag(mModifLightRays); + stream->write(mLightRayMod); + } + if (stream->writeFlag(mask & VolumetricFogMask)) + { + stream->writeFlag(mIgnoreWater); + stream->writeFlag(mReflect); + stream->write(mFogReflStrength); + stream->writeFlag(mResizing); + stream->write(mMinDisplaySize); + stream->write(mFadeSize); + } + if (stream->writeFlag(mask & FogShapeMask)) + { + stream->writeString(mShapeName); + mathWrite(*stream, getTransform()); + mathWrite(*stream, getScale()); + if (!mShapeName || mShapeName[0] == '\0') + return retMask; + Resource mShape; + mShape = ResourceManager::get().load(mShapeName); + if (bool(mShape) == false) + return retMask; + mObjBox = mShape->bounds; + mRadius = mShape->radius; + resetWorldBox(); + mObjSize = mWorldBox.getGreatestDiagonalLength(); + mObjScale = getScale(); + mInvScale = (1.0f / getMax(getMax(mObjScale.x, mObjScale.y), mObjScale.z)); + } + return retMask; +} + +void VolumetricFog::unpackUpdate(NetConnection *con, BitStream *stream) +{ + Parent::unpackUpdate(con, stream); + MatrixF mat; + VectorF scale; + VectorF mOldScale = getScale(); + String oldTextureName = mTextureName; + StringTableEntry oldShape = mShapeName; + + if (stream->readFlag())// Fog color + stream->read(&mFogColor); + if (stream->readFlag())// Fog Density + { + stream->read(&mFogDensity); + if (isTicking()) + { + char buf[20]; + dSprintf(buf, sizeof(buf), "%3.7f", mFogDensity); + Con::setVariable("$VolumetricFog::density", buf); + } + } + if (stream->readFlag())// Fog Modulation + { + stream->read(&mTextureName); + stream->read(&mTexTiles); + mTexTiles = mFabs(mTexTiles); + stream->read(&mStrength); + mathRead(*stream, &mSpeed); + mSpeed1.set(mSpeed.x, mSpeed.y); + mSpeed2.set(mSpeed.z, mSpeed.w); + + if (isProperlyAdded()) + { + if (oldTextureName != mTextureName) + InitTexture(); + if (oldTextureName.isNotEmpty() && mTextureName.isEmpty()) + { + mIsTextured = false; + mTexture.free(); + } + } + } + if (stream->readFlag())//Fog PostFX + { + mUseGlow = stream->readFlag(); + stream->read(&mGlowStrength); + mModifLightRays = stream->readFlag(); + stream->read(&mLightRayMod); + if (isTicking()) + { + char buf[20]; + dSprintf(buf, sizeof(buf), "%3.7f", mGlowStrength); + Con::setVariable("$VolFogGlowPostFx::glowStrength", buf); + if (mUseGlow && !glowFX->isEnabled()) + glowFX->enable(); + if (!mUseGlow && glowFX->isEnabled()) + glowFX->disable(); + if (mModifLightRays) + { + char buf[20]; + dSprintf(buf, sizeof(buf), "%3.7f", mOldLightRayStrength * mLightRayMod); + Con::setVariable("$LightRayPostFX::brightScalar", buf); + } + if (!mModifLightRays) + { + char buf[20]; + dSprintf(buf, sizeof(buf), "%3.7f", mOldLightRayStrength); + Con::setVariable("$LightRayPostFX::brightScalar", buf); + } + } + } + if (stream->readFlag())//Volumetric Fog + { + mIgnoreWater = stream->readFlag(); + mReflect = stream->readFlag(); + stream->read(&mFogReflStrength); + mResizing = stream->readFlag(); + stream->read(&mMinDisplaySize); + stream->read(&mFadeSize); + } + if (stream->readFlag())//Fog shape + { + mShapeName = stream->readSTString(); + mathRead(*stream, &mat); + mathRead(*stream, &scale); + if (strcmp(oldShape, mShapeName) != 0) + { + mIsVBDirty = true; + mShapeLoaded = LoadShape(); + } + setScale(scale); + setTransform(mat); + ColBox.set(getTransform(), (mObjBox.getExtents() * getScale() * COLBOX_SCALE)); + mObjSize = mWorldBox.getGreatestDiagonalLength(); + mObjScale = getScale(); + mInvScale = (1.0f / getMax(getMax(mObjScale.x, mObjScale.y), mObjScale.z)); + } +} + +void VolumetricFog::processTick(const Move* move) +{ + Parent::processTick(move); + mCounter++; + if ( mGlowing==1 && mCurGlow < mGlowStrength ) + { + mCurGlow += (mGlowStrength / 10.0); + char buf[20]; + dSprintf(buf, sizeof(buf), "%3.7f", mCurGlow); + Con::setVariable("$VolFogGlowPostFx::glowStrength", buf); + } + else if ( mGlowing == 2 && mCurGlow > 0.0f ) + { + mCurGlow -= (mGlowStrength / 5.0f); + if (mCurGlow <= 0.0f) + { + glowFX->disable(); + mGlowing = 0; + setProcessTick(false); + return; + } + else + { + char buf[20]; + dSprintf(buf, sizeof(buf), "%3.7f", mCurGlow); + Con::setVariable("$VolFogGlowPostFx::glowStrength", buf); + } + } + if (mCounter == 3) + { + ShapeBase* control = static_cast(conn->getControlObject()); + MatrixF xfm; + control->getRenderEyeTransform(&xfm); + Point3F pos = xfm.getPosition(); + if (!ColBox.isContained(pos)) + _leaveFog(control); + mCounter = 0; + } +} + +void VolumetricFog::_enterFog(ShapeBase *control) +{ + if (mUseGlow) + { + if (glowFX) + { + mCurGlow = 0.0f; + Con::setVariable("$VolFogGlowPostFx::glowStrength", "0.0"); + glowFX->enable(); + mGlowing = 1; + } + } + if (mModifLightRays) + { + char buf[20]; + dSprintf(buf, sizeof(buf), "%3.7f", mOldLightRayStrength * mLightRayMod); + Con::setVariable("$LightRayPostFX::brightScalar", buf); + } + mCounter = 0; + char buf[20]; + dSprintf(buf, sizeof(buf), "%3.7f", mFogDensity); + Con::setVariable("$VolumetricFog::density", buf); + setProcessTick(true); + if (control) + onEnterFog_callback(control->getId()); +} + +void VolumetricFog::_leaveFog(ShapeBase *control) +{ + mCamInFog = false; + Con::setVariable("$VolumetricFog::density", "0.0"); + if (mModifLightRays) + { + char buf[20]; + dSprintf(buf, sizeof(buf), "%3.7f", mOldLightRayStrength); + Con::setVariable("$LightRayPostFX::brightScalar", buf); + } + if (mUseGlow) + { + if (glowFX && mGlowing != 2) + { + mCurGlow = mGlowStrength; + mGlowing = 2; + if (control) + onLeaveFog_callback(control->getId()); + } + } + else + { + setProcessTick(false); + if (control) + onLeaveFog_callback(control->getId()); + } +} + +//----------------------------------------------------------------------------- +// Setting up the renderers +//----------------------------------------------------------------------------- + +bool VolumetricFog::setupRenderer() +{ + // Search for the prepass rendertarget and shadermacros. + mPrepassTarget = NamedTexTarget::find("prepass"); + if (!mPrepassTarget.isValid()) + { + Con::errorf("VolumetricFog::setupRenderer - could not find PrepassTarget"); + return false; + } + + Vector macros; + if (mPrepassTarget) + mPrepassTarget->getShaderMacros(¯os); + + // Search the depth and frontbuffers which are created by the VolumetricFogRTManager + + mDepthBufferTarget = NamedTexTarget::find("volfogdepth"); + if (!mDepthBufferTarget.isValid()) + { + Con::errorf("VolumetricFog::setupRenderer - could not find depthbuffer"); + return false; + } + + mFrontBufferTarget = NamedTexTarget::find("volfogfront"); + if (!mFrontBufferTarget.isValid()) + { + Con::errorf("VolumetricFog::setupRenderer - could not find frontbuffer"); + return false; + } + + // Find and setup the prepass Shader + + ShaderData *shaderData; + mPrePassShader = Sim::findObject("VolumetricFogPrePassShader", shaderData) ? + shaderData->getShader() : NULL; + if (!mPrePassShader) + { + Con::errorf("VolumetricFog::setupRenderer - could not find VolumetricFogPrePassShader"); + return false; + } + + // Create ShaderConstBuffer and Handles + + mPPShaderConsts = mPrePassShader->allocConstBuffer(); + if (mPPShaderConsts.isNull()) + { + Con::errorf("VolumetricFog::setupRenderer - could not allocate ShaderConstants 1."); + return false; + } + + mPPModelViewProjSC = mPrePassShader->getShaderConstHandle("$modelView"); + + // Find and setup the VolumetricFog Shader + + shaderData = NULL; + mShader = Sim::findObject("VolumetricFogShader", shaderData) ? + shaderData->getShader(macros) : NULL; + if (!mShader) + { + Con::errorf("VolumetricFog::setupRenderer - could not find VolumetricFogShader"); + return false; + } + + // Create ShaderConstBuffer and Handles + + mShaderConsts = mShader->allocConstBuffer(); + if (mShaderConsts.isNull()) + { + Con::errorf("VolumetricFog::setupRenderer - could not allocate ShaderConstants 2."); + return false; + } + + mModelViewProjSC = mShader->getShaderConstHandle("$modelView"); + mFadeSizeSC = mShader->getShaderConstHandle("$fadesize"); + mFogColorSC = mShader->getShaderConstHandle("$fogColor"); + mFogDensitySC = mShader->getShaderConstHandle("$fogDensity"); + mPreBias = mShader->getShaderConstHandle("$preBias"); + mAccumTime = mShader->getShaderConstHandle("$accumTime"); + mIsTexturedSC = mShader->getShaderConstHandle("$textured"); + mTexTilesSC = mShader->getShaderConstHandle("$numtiles"); + mModStrengthSC = mShader->getShaderConstHandle("$modstrength"); + mModSpeedSC = mShader->getShaderConstHandle("$modspeed"); + mViewPointSC = mShader->getShaderConstHandle("$viewpoint"); + mTexScaleSC = mShader->getShaderConstHandle("$texscale"); + mAmbientColorSC = mShader->getShaderConstHandle("$ambientColor"); + + // Find and setup the reflection Shader + + shaderData = NULL; + mReflectionShader = Sim::findObject("VolumetricFogReflectionShader", shaderData) ? + shaderData->getShader() : NULL; + if (!mReflectionShader) + { + Con::errorf("VolumetricFog::setupRenderer - could not find VolumetricFogReflectionShader"); + return false; + } + + mReflShaderConsts = mReflectionShader->allocConstBuffer(); + if (mReflShaderConsts.isNull()) + { + Con::errorf("VolumetricFog::setupRenderer - could not allocate ShaderConstants for VolumetricFogReflectionShader."); + return false; + } + + mReflModelViewProjSC = mReflectionShader->getShaderConstHandle("$modelView"); + mReflFogColorSC = mReflectionShader->getShaderConstHandle("$fogColor"); + mReflFogDensitySC = mReflectionShader->getShaderConstHandle("$fogDensity"); + mReflFogStrengthSC = mReflectionShader->getShaderConstHandle("$reflStrength"); + + // Create the prepass StateBlock + + desc_preD.setCullMode(GFXCullCW); + desc_preD.setBlend(true); + desc_preD.setZReadWrite(false, false); + desc_preD.stencilEnable = false; + desc_preF.setCullMode(GFXCullCCW); + desc_preF.setBlend(true); + desc_preF.setZReadWrite(true, false); + desc_preF.stencilEnable = false; + + // Create the VolumetricFog StateBlock + + descD.setCullMode(GFXCullCW); + descD.setBlend(true); + descD.setZReadWrite(false, false);// desc.setZReadWrite(true, false); + + // prepassBuffer sampler + descD.samplersDefined = true; + descD.samplers[0].addressModeU = GFXAddressClamp; + descD.samplers[0].addressModeV = GFXAddressClamp; + descD.samplers[0].addressModeW = GFXAddressClamp; + descD.samplers[0].magFilter = GFXTextureFilterLinear; + descD.samplers[0].minFilter = GFXTextureFilterLinear; + descD.samplers[0].mipFilter = GFXTextureFilterLinear; + descD.samplers[0].textureColorOp = GFXTOPDisable; + + // DepthBuffer sampler + descD.samplers[1].addressModeU = GFXAddressClamp; + descD.samplers[1].addressModeV = GFXAddressClamp; + descD.samplers[1].addressModeW = GFXAddressClamp; + descD.samplers[1].magFilter = GFXTextureFilterLinear; + descD.samplers[1].minFilter = GFXTextureFilterLinear; + descD.samplers[1].mipFilter = GFXTextureFilterLinear; + descD.samplers[1].textureColorOp = GFXTOPModulate; + + // FrontBuffer sampler + descD.samplers[2].addressModeU = GFXAddressClamp; + descD.samplers[2].addressModeV = GFXAddressClamp; + descD.samplers[2].addressModeW = GFXAddressClamp; + descD.samplers[2].magFilter = GFXTextureFilterLinear; + descD.samplers[2].minFilter = GFXTextureFilterLinear; + descD.samplers[2].mipFilter = GFXTextureFilterLinear; + descD.samplers[2].textureColorOp = GFXTOPModulate; + + // animated density modifier map sampler + descD.samplers[3].addressModeU = GFXAddressWrap; + descD.samplers[3].addressModeV = GFXAddressWrap; + descD.samplers[3].addressModeW = GFXAddressWrap; + descD.samplers[3].magFilter = GFXTextureFilterLinear; + descD.samplers[3].minFilter = GFXTextureFilterLinear; + descD.samplers[3].mipFilter = GFXTextureFilterLinear; + descD.samplers[3].textureColorOp = GFXTOPModulate; + + dMemcpy(&descF, &descD, sizeof(GFXStateBlockDesc)); + descF.setCullMode(GFXCullCCW); + descF.setBlend(true); + descF.setZReadWrite(true, false); + + desc_refl.setCullMode(GFXCullCCW); + desc_refl.setBlend(true); + desc_refl.setZReadWrite(true, false); + + mStateblock_preD = GFX->createStateBlock(desc_preD); + mStateblock_preF = GFX->createStateBlock(desc_preF); + mStateblockD = GFX->createStateBlock(descD); + mStateblockF = GFX->createStateBlock(descF); + mStateblock_refl = GFX->createStateBlock(desc_refl); + + // Create Rendertarget + + z_buf = GFX->allocRenderToTextureTarget(); + if (z_buf == NULL) + { + Con::errorf("VolumetricFog::setupRenderer - Could not create Render Target"); + return false; + } + + return true; +} + +void VolumetricFog::prepRenderImage(SceneRenderState *state) +{ + if (!mShapeLoaded || mFogDensity <= 0.0f || mResizing) + return; + + if (!state->isDiffusePass()) + { + if (!state->isReflectPass()) + return; + } + + PROFILE_SCOPE(VolumetricFog_prepRenderImage); + + // Time critical therefore static_cast + ShapeBase* control = static_cast(conn->getControlObject()); + if (control->getWaterCoverage() >= 0.9f && !mIgnoreWater) + return; + + camPos = state->getCameraPosition(); + F32 dist = (camPos - getBoxCenter()).len(); + F32 scaleFactor = dist * mInvScale; + if (scaleFactor <= 0.0f) + { + if (mCurDetailLevel != 0) + UpdateBuffers(0); + } + const F32 pixelScale = state->getViewport().extent.y / 300.0f; + + mPixelSize = (mRadius / scaleFactor) * state->getWorldToScreenScale().y * pixelScale; + if (mPixelSize < mMinDisplaySize) + return; + if (mNumDetailLevels > 1) + { + if ((det_size[mCurDetailLevel].det_size > mPixelSize) && (mCurDetailLevel < mNumDetailLevels - 1)) + UpdateBuffers(mCurDetailLevel + 1); + else if (mCurDetailLevel > 0) + { + if (mPixelSize >= det_size[mCurDetailLevel - 1].det_size) + UpdateBuffers(mCurDetailLevel - 1); + } + } + + if (state->isReflectPass() && mReflect) + { + ObjectRenderInst *ri = state->getRenderPass()->allocInst(); + ri->renderDelegate.bind(this, &VolumetricFog::reflect_render); + ri->type = RenderPassManager::RIT_VolumetricFog; + ri->translucentSort = true; + ri->sortDistSq = getRenderWorldBox().getSqDistanceToPoint(camPos); + if (dist < 1.0f) + ri->defaultKey = 1; + else + ri->defaultKey = U32(dist); + state->getRenderPass()->addInst(ri); + return; + } + else if (state->isDiffusePass()) + { + viewDist = state->getFarPlane(); + mFOV = state->getCameraFrustum().getFov() / M_PI_F; + Point3F mEyeVec = state->getVectorEye() * viewDist; + + mViewPoint.x = ((mAtan2(mEyeVec.x, mEyeVec.y) / M_PI_F) + 1.0f) * mTexTiles; + mViewPoint.y = (0.5f - (mAsin(mEyeVec.z) / M_PI_F)) * mTexTiles; + + bool isInside = ColBox.isContained(camPos); + if (isInside && !mCamInFog) + { + mCamInFog = true; + _enterFog(control); + } + else if (!isInside && mCamInFog) + mCamInFog = false; + + ObjectRenderInst *ri = state->getRenderPass()->allocInst(); + ri->renderDelegate.bind(this, &VolumetricFog::render); + ri->type = RenderPassManager::RIT_VolumetricFog; + ri->translucentSort = true; + ri->sortDistSq = getRenderWorldBox().getSqDistanceToPoint(camPos); + if (dist < 1.0f) + ri->defaultKey = 1; + else + ri->defaultKey = U32(dist); + state->getRenderPass()->addInst(ri); + return; + } + return; +} + +void VolumetricFog::render(ObjectRenderInst *ri, SceneRenderState *state, BaseMatInstance *overrideMat) +{ + if (overrideMat || !mShapeLoaded || !isClientObject() || mResizing) + return; + + PROFILE_SCOPE(VolumetricFog_Render); + + GFXTransformSaver saver; + GFX->setVertexBuffer(mVB); + GFX->setPrimitiveBuffer(mPB); + + MatrixF mat = getRenderTransform(); + mat.scale(mObjScale); + GFX->multWorld(mat); + + GFX->setShader(mPrePassShader); + GFX->setShaderConstBuffer(mPPShaderConsts); + GFX->setStateBlock(mStateblock_preD); + + // Set all the shader consts... + + MatrixF xform(GFX->getProjectionMatrix()); + xform *= GFX->getViewMatrix(); + xform *= GFX->getWorldMatrix(); + + mPPShaderConsts->setSafe(mPPModelViewProjSC, xform); + + LightInfo *lightinfo = LIGHTMGR->getSpecialLight(LightManager::slSunLightType); + const ColorF &sunlight = state->getAmbientLightColor(); + + Point3F ambientColor(sunlight.red, sunlight.green, sunlight.blue); + mShaderConsts->setSafe(mAmbientColorSC, ambientColor); + + GFXTextureObject *mDepthBuffer = mDepthBufferTarget ? mDepthBufferTarget->getTexture(0) : NULL; + GFXTextureObject *mFrontBuffer = mFrontBufferTarget ? mFrontBufferTarget->getTexture(0) : NULL; + + GFX->pushActiveRenderTarget(); + + //render backside to target mDepthBuffer + z_buf->attachTexture(GFXTextureTarget::DepthStencil, GFXTextureTarget::sDefaultDepthStencil); + z_buf->attachTexture(GFXTextureTarget::Color0, mDepthBuffer); + + GFX->setActiveRenderTarget(z_buf); + GFX->clear(GFXClearStencil | GFXClearTarget , ColorI(0,0,0,0), 1.0f, 0); + + GFX->drawPrimitive(0); + z_buf->resolve(); + + //render frontside to target mFrontBuffer + z_buf->attachTexture(GFXTextureTarget::DepthStencil, GFXTextureTarget::sDefaultDepthStencil); + z_buf->attachTexture(GFXTextureTarget::Color0, mFrontBuffer); + GFX->clear(GFXClearStencil | GFXClearTarget, ColorI(0, 0, 0, 0), 1.0f, 0); + + GFX->setStateBlock(mStateblock_preF); + + GFX->drawPrimitive(0); + z_buf->resolve(); + + GFX->popActiveRenderTarget(); + z_buf->attachTexture(GFXTextureTarget::Color0, NULL); + + //render Volumetric Fog + GFX->setShader(mShader); + GFX->setShaderConstBuffer(mShaderConsts); + + mShaderConsts->setSafe(mModelViewProjSC, xform); + if (mFadeSize > 0.0f) + mShaderConsts->setSafe(mFadeSizeSC, mClampF(mPixelSize / mFadeSize, 0.0f, 1.0f)); + else + mShaderConsts->setSafe(mFadeSizeSC, 1.0f); + mShaderConsts->setSafe(mFogColorSC, mFogColor); + mShaderConsts->setSafe(mFogDensitySC, mFogDensity); + mShaderConsts->setSafe(mPreBias, viewDist); + mShaderConsts->setSafe(mAccumTime, (F32)Sim::getCurrentTime() / 1000.0f); + mShaderConsts->setSafe(mModStrengthSC, mStrength); + mShaderConsts->setSafe(mModSpeedSC, mSpeed); + mShaderConsts->setSafe(mViewPointSC, mViewPoint); + mShaderConsts->setSafe(mTexScaleSC, mTexScale * mFOV); + mShaderConsts->setSafe(mTexTilesSC, mTexTiles); + + GFXTextureObject *prepasstex = mPrepassTarget ? mPrepassTarget->getTexture(0) : NULL; + + GFX->setTexture(0, prepasstex); + GFX->setTexture(1, mDepthBuffer); + GFX->setTexture(2, mFrontBuffer); + + if (mIsTextured && mStrength > 0.0f) + { + GFX->setTexture(3, mTexture); + mShaderConsts->setSafe(mIsTexturedSC, 1.0f); + } + else + mShaderConsts->setSafe(mIsTexturedSC, 0.0f); + + if (mCamInFog) + { + /*GFXLockedRect *rect=mDepthBuffer->lock(); + U32 pixoffset = 0;// 1572864 + (512 * 4); + U8 red = rect->bits[pixoffset]; + U8 green = rect->bits[pixoffset+1]; + U8 blue = rect->bits[pixoffset+2]; + U8 alpha = rect->bits[pixoffset+3]; + mDepthBuffer->unlock(); + S32 lval = ((alpha << 24) + (blue << 16) + (green << 8) + (red)); + F32 fval = ((F32)lval / S32_MAX); + Con::printf("Color %d %d %d %d %d %f", red, green, blue, alpha, lval, fval);*/ + GFX->setStateBlock(mStateblockD); + } + else + GFX->setStateBlock(mStateblockF); + + GFX->drawPrimitive(0); +} + +void VolumetricFog::reflect_render(ObjectRenderInst *ri, SceneRenderState *state, BaseMatInstance *overrideMat) +{ + if (overrideMat || !mShapeLoaded || !isClientObject() || mResizing || (mFogReflStrength==0.0f)) + return; + + GFXTransformSaver saver; + GFX->setVertexBuffer(mVB); + GFX->setPrimitiveBuffer(mPB); + + MatrixF mat = getRenderTransform(); + mat.scale(mObjScale); + GFX->multWorld(mat); + + GFX->setShader(mReflectionShader); + GFX->setShaderConstBuffer(mReflShaderConsts); + GFX->setStateBlock(mStateblock_refl); + + // Set all the shader consts... + MatrixF xform(GFX->getProjectionMatrix()); + xform *= GFX->getViewMatrix(); + xform *= GFX->getWorldMatrix(); + + mReflShaderConsts->setSafe(mReflModelViewProjSC, xform); + mReflShaderConsts->setSafe(mReflFogColorSC, mFogColor); + mReflShaderConsts->setSafe(mReflFogDensitySC, mFogDensity); + mReflShaderConsts->setSafe(mReflFogStrengthSC, mFogReflStrength); + + GFX->drawPrimitive(0); +} + +//----------------------------------------------------------------------------- +// InitTexture is called whenever a modulation texture is added to the object +//----------------------------------------------------------------------------- + +void VolumetricFog::InitTexture() +{ + mIsTextured = false; + + if (mTextureName.isNotEmpty()) + mTexture.set(mTextureName, &GFXDefaultStaticDiffuseProfile, "VolumetricFogMod"); + + if (!mTexture.isNull()) + { + mIsTextured = true; + + F32 width = (F32)mPlatformWindow->getClientExtent().x; + F32 height = (F32)mPlatformWindow->getClientExtent().y; + + if (!mPlatformWindow->isFullscreen()) + height -= 20;//subtract caption bar from rendertarget size. + + mTexScale.x = 2.0f - ((F32)mTexture.getWidth() / width); + mTexScale.y = 2.0f - ((F32)mTexture.getHeight() / height); + } +} + +void VolumetricFog::setFogColor(ColorF color) +{ + mFogColor.set(255 * color.red,255 * color.green,255 * color.blue); + setMaskBits(FogColorMask); +} + +void VolumetricFog::setFogColor(ColorI color) +{ + mFogColor = color; + setMaskBits(FogColorMask); +} + +void VolumetricFog::setFogDensity(F32 density) +{ + if (density < 0.0f) + density = 0.0f; + mFogDensity = density; + setMaskBits(FogDensityMask); +} + +void VolumetricFog::setFogModulation(F32 strength,Point2F speed1,Point2F speed2) +{ + mStrength = strength; + mSpeed1 = speed1; + mSpeed2 = speed2; + mSpeed.set(speed1.x, speed1.y, speed2.x, speed2.y); + setMaskBits(FogModulationMask); +} + +void VolumetricFog::setFogGlow(bool on_off, F32 strength) +{ + mUseGlow = on_off; + mGlowStrength = strength; + setMaskBits(FogPostFXMask); +} + +void VolumetricFog::setFogLightray(bool on_off, F32 strength) +{ + mModifLightRays = on_off; + mLightRayMod = strength; + setMaskBits(FogPostFXMask); +} + +bool VolumetricFog::isInsideFog() +{ + return mCamInFog; +} + +DefineEngineMethod(VolumetricFog, SetFogColorF, void, (ColorF new_color), , +"@brief Changes the color of the fog\n\n." +"@params new_color the new fog color (rgb 0.0 - 1.0, a is ignored.") +{ + object->setFogColor(new_color); +} + +DefineEngineMethod(VolumetricFog, SetFogColor, void, (ColorI new_color), , +"@brief Changes the color of the fog\n\n." +"@params new_color the new fog color (rgb 0-255, a is ignored.") +{ + object->setFogColor(new_color); +} + +DefineEngineMethod(VolumetricFog, SetFogDensity, void, (F32 new_density), , +"@brief Changes the density of the fog\n\n." +"@params new_density the new fog density.") +{ + object->setFogDensity(new_density); +} + +DefineEngineMethod(VolumetricFog, SetFogModulation, void, (F32 new_strenght, Point2F new_speed1, Point2F new_speed2), , +"@brief Changes the modulation of the fog\n\n." +"@params new_strenght the new strength of the modulation.\n" +"@params new_speed1 the new speed (x y) of the modulation layer 1.\n" +"@params new_speed2 the new speed (x y) of the modulation layer 2.\n") +{ + object->setFogModulation(new_strenght, new_speed1, new_speed2); +} + +DefineEngineMethod(VolumetricFog, SetFogGlow, void, (bool on_off,F32 strength), , +"@brief Changes the glow postfx when inside the fog\n\n." +"@params on_off set to true to enable glow.\n" +"@params strength glow strength.\n") +{ + object->setFogGlow(on_off, strength); +} + +DefineEngineMethod(VolumetricFog, SetFogLightray, void, (bool on_off, F32 strength), , +"@brief Changes the lightrays postfx when inside the fog\n\n." +"@params on_off set to true to modification of the lightray postfx.\n" +"@params strength lightray strength.\n") +{ + object->setFogLightray(on_off, strength); +} + +DefineEngineMethod(VolumetricFog, isInsideFog, bool, (), , +"@brief returns true if control object is inside the fog\n\n.") +{ + return object->isInsideFog(); +} \ No newline at end of file diff --git a/Engine/source/environment/VolumetricFog.h b/Engine/source/environment/VolumetricFog.h new file mode 100644 index 000000000..ef2f39929 --- /dev/null +++ b/Engine/source/environment/VolumetricFog.h @@ -0,0 +1,243 @@ +//----------------------------------------------------------------------------- +// Copyright (c) 2012 GarageGames, LLC +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. +//----------------------------------------------------------------------------- + +#ifndef _VolumetricFog_H_ +#define _VolumetricFog_H_ + +#ifndef _SCENEOBJECT_H_ +#include "scene/sceneObject.h" +#endif +#ifndef _MATTEXTURETARGET_H_ +#include "materials/matTextureTarget.h" +#endif +#ifndef _GFXSHADER_H_ +#include "gfx/gfxShader.h" +#endif +#ifndef _GFXTARGET_H_ +#include "gfx/gfxTarget.h" +#endif +#ifndef _GFXVERTEXBUFFER_H_ +#include "gfx/gfxVertexBuffer.h" +#endif +#ifndef _TSSHAPE_H_ +#include "ts/tsShape.h" +#endif +#ifndef _POST_EFFECT_H_ +#include "postFx/postEffect.h" +#endif + +#include "gui/core/guiCanvas.h" + +class VolumetricFogRTManager; + +class VolumetricFog : public SceneObject +{ + typedef SceneObject Parent; + + // Maskbits for updating + enum + { + VolumetricFogMask = Parent::NextFreeMask, + FogColorMask = Parent::NextFreeMask << 1, + FogDensityMask = Parent::NextFreeMask << 2, + FogModulationMask = Parent::NextFreeMask << 3, + FogPostFXMask = Parent::NextFreeMask << 4, + FogShapeMask = Parent::NextFreeMask << 5, + NextFreeMask = Parent::NextFreeMask << 6 + }; + +// Struct which holds the shape details + struct meshes + { + F32 det_size; + S32 sub_shape; + S32 obj_det; + U32 num_verts; + GFXVertexPNTT *verts; + Vector *piArray; + Vector *indices; + }; + + protected: + // Rendertargets; + GFXTextureTargetRef z_buf; + NamedTexTargetRef mPrepassTarget; + NamedTexTargetRef mDepthBufferTarget; + NamedTexTargetRef mFrontBufferTarget; + + // Fog Modulation texture + GFXTexHandle mTexture; + + // Shaders + GFXShaderRef mShader; + GFXShaderRef mPrePassShader; + GFXShaderRef mReflectionShader; + + // Stateblocks + GFXStateBlockDesc descD; + GFXStateBlockDesc descF; + GFXStateBlockDesc desc_preD; + GFXStateBlockDesc desc_preF; + GFXStateBlockDesc desc_refl; + + GFXStateBlockRef mStateblockD; + GFXStateBlockRef mStateblockF; + GFXStateBlockRef mStateblock_preD; + GFXStateBlockRef mStateblock_preF; + GFXStateBlockRef mStateblock_refl; + + // Shaderconstants + GFXShaderConstBufferRef mShaderConsts; + GFXShaderConstHandle *mModelViewProjSC; + GFXShaderConstHandle *mFadeSizeSC; + GFXShaderConstHandle *mFogColorSC; + GFXShaderConstHandle *mFogDensitySC; + GFXShaderConstHandle *mPreBias; + GFXShaderConstHandle *mAccumTime; + GFXShaderConstHandle *mIsTexturedSC; + GFXShaderConstHandle *mModSpeedSC; + GFXShaderConstHandle *mModStrengthSC; + GFXShaderConstHandle *mViewPointSC; + GFXShaderConstHandle *mTexScaleSC; + GFXShaderConstHandle *mTexTilesSC; + + GFXShaderConstBufferRef mPPShaderConsts; + GFXShaderConstHandle *mPPModelViewProjSC; + + GFXShaderConstHandle *mAmbientColorSC; + + GFXShaderConstBufferRef mReflShaderConsts; + GFXShaderConstHandle *mReflModelViewProjSC; + GFXShaderConstHandle *mReflFogColorSC; + GFXShaderConstHandle *mReflFogDensitySC; + GFXShaderConstHandle *mReflFogStrengthSC; + + // Vertex and Prim. Buffer + GFXVertexBufferHandle mVB; + GFXPrimitiveBufferHandle mPB; + + // Fog volume data; + StringTableEntry mShapeName; + ColorI mFogColor; + F32 mFogDensity; + bool mIgnoreWater; + bool mReflect; + Vector det_size; + bool mShapeLoaded; + F32 mPixelSize; + F32 mFadeSize; + U32 mCurDetailLevel; + U32 mNumDetailLevels; + F32 mObjSize; + F32 mRadius; + OrientedBox3F ColBox; + VectorF mObjScale; + F32 mMinDisplaySize; + F32 mInvScale; + + // Fog Modulation data + String mTextureName; + bool mIsTextured; + F32 mTexTiles; + F32 mStrength; + Point2F mSpeed1; + Point2F mSpeed2; + Point4F mSpeed; + Point2F mTexScale; + + // Fog Rendering data + Point3F camPos; + Point2F mViewPoint; + F32 mFOV; + F32 viewDist; + bool mIsVBDirty; + bool mIsPBDirty; + bool mCamInFog; + bool mResizing; + PlatformWindow *mPlatformWindow; + + // Reflections + F32 mFogReflStrength; + + // PostFX + PostEffect *glowFX; + bool mUseGlow; + F32 mGlowStrength; + U8 mGlowing; + F32 mCurGlow; + + bool mModifLightRays; + F32 mLightRayMod; + F32 mOldLightRayStrength; + + GameConnection* conn; + U32 mCounter; + + void ResizeRT(PlatformWindow *win, bool resize); + + protected: + // Protected methods + bool onAdd(); + void onRemove(); + void handleResize(VolumetricFogRTManager *RTM, bool resize); + void handleCanvasResize(GuiCanvas* canvas); + + bool LoadShape(); + bool setupRenderer(); + void InitTexture(); + bool UpdateBuffers(U32 dl,bool force=true); + + void processTick(const Move *move); + void _enterFog(ShapeBase *control); + void _leaveFog(ShapeBase *control); + + public: + // Public methods + VolumetricFog(); + ~VolumetricFog(); + + static void initPersistFields(); + virtual void inspectPostApply(); + + U32 packUpdate(NetConnection *conn, U32 mask, BitStream *stream); + void unpackUpdate(NetConnection *conn, BitStream *stream); + + void prepRenderImage(SceneRenderState* state); + void render(ObjectRenderInst *ri, SceneRenderState *state, BaseMatInstance *overrideMat); + void reflect_render(ObjectRenderInst *ri, SceneRenderState *state, BaseMatInstance *overrideMat); + + // Methods for modifying & networking various fog elements + // Used in script + void setFogColor(ColorF color); + void setFogColor(ColorI color); + void setFogDensity(F32 density); + void setFogModulation(F32 strength, Point2F speed1, Point2F speed2); + void setFogGlow(bool on_off, F32 strength); + void setFogLightray(bool on_off, F32 strength); + bool isInsideFog(); + + DECLARE_CONOBJECT(VolumetricFog); + + DECLARE_CALLBACK(void, onEnterFog, (SimObjectId obj)); + DECLARE_CALLBACK(void, onLeaveFog, (SimObjectId obj)); +}; +#endif \ No newline at end of file diff --git a/Engine/source/environment/VolumetricFogRTManager.cpp b/Engine/source/environment/VolumetricFogRTManager.cpp new file mode 100644 index 000000000..2a927cc09 --- /dev/null +++ b/Engine/source/environment/VolumetricFogRTManager.cpp @@ -0,0 +1,299 @@ +//----------------------------------------------------------------------------- +// Copyright (c) 2012 GarageGames, LLC +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. +//----------------------------------------------------------------------------- + +//----------------------------------------------------------------------------- +// Volumetric Fog Rendertarget Manager +// +// Creates and maintains one set of rendertargets to be used by every +// VolumetricFog object in the scene. +// +// Will be loaded at startup end removed when ending game. +// +//----------------------------------------------------------------------------- + +#include "VolumetricFogRTManager.h" +#include "core/module.h" +#include "scene/sceneManager.h" +#include "windowManager/platformWindowMgr.h" +#include "console/engineAPI.h" +#include "gui/core/guiCanvas.h" + +MODULE_BEGIN(VolumetricFogRTManager) + +MODULE_INIT_AFTER(Scene) +MODULE_SHUTDOWN_BEFORE(Scene) + +MODULE_INIT +{ + gVolumetricFogRTManager = new VolumetricFogRTManager; + gClientSceneGraph->addObjectToScene(gVolumetricFogRTManager); +} + +MODULE_SHUTDOWN +{ + gClientSceneGraph->removeObjectFromScene(gVolumetricFogRTManager); + SAFE_DELETE(gVolumetricFogRTManager); +} + +MODULE_END; + +ConsoleDocClass( VolumetricFogRTManager, +"@brief Creates and maintains one set of rendertargets to be used by every\n" +"VolumetricFog object in the scene.\n\n" +"Will be loaded at startup end removed when ending game.\n\n" +"Methods:\n" +" get() returns the currently loaded VolumetricFogRTManager, also accessible\n" +" through VFRTM define.\n" +" Init() Initializes the rendertargets, called when a VolumetricFog object is\n" +" added to the scene.\n" +" isInitialed() returns true if Rendertargets are present, false if not, then\n" +" Init() should be called to create the rendertargets.\n" +" setQuality(U32 Quality) Normally a rendertarget has the same size as the view,\n" +" with this method you can scale down the size of it.\n" +" Be aware that scaling down will introduce renderartefacts.\n" +"@ingroup Atmosphere" +); + +VolumetricFogRTMResizeSignal VolumetricFogRTManager::smVolumetricFogRTMResizeSignal; + +VolumetricFogRTManager *gVolumetricFogRTManager = NULL; + +S32 VolumetricFogRTManager::mTargetScale = 1; + +IMPLEMENT_CONOBJECT(VolumetricFogRTManager); + +VolumetricFogRTManager::VolumetricFogRTManager() +{ + setGlobalBounds(); + mTypeMask |= EnvironmentObjectType; + mNetFlags.set(IsGhost); + mIsInitialized = false; + mNumFogObjects = 0; +} + +VolumetricFogRTManager::~VolumetricFogRTManager() +{ + if (mFrontTarget.isRegistered()) + mFrontTarget.unregister(); + + if (mDepthTarget.isRegistered()) + mDepthTarget.unregister(); + + if (mDepthBuffer.isValid()) + mDepthBuffer->kill(); + + if (mFrontBuffer.isValid()) + mFrontBuffer->kill(); +} + +void VolumetricFogRTManager::onSceneRemove() +{ + if (mIsInitialized) + mPlatformWindow->getScreenResChangeSignal().remove(this, &VolumetricFogRTManager::ResizeRT); +} + +void VolumetricFogRTManager::onRemove() +{ + removeFromScene(); + Parent::onRemove(); +} + +void VolumetricFogRTManager::consoleInit() +{ + Con::addVariable("$pref::VolumetricFog::Quality", TypeS32, &mTargetScale, + "The scale of the rendertargets.\n" + "@ingroup Rendering\n"); +} + +bool VolumetricFogRTManager::Init() +{ + if (mIsInitialized) + { + Con::errorf("VolumetricFogRTManager allready initialized!!"); + return true; + } + + GuiCanvas* cv = dynamic_cast(Sim::findObject("Canvas")); + if (cv == NULL) + { + Con::errorf("VolumetricFogRTManager::Init() - Canvas not found!!"); + return false; + } + + mPlatformWindow = cv->getPlatformWindow(); + mPlatformWindow->getScreenResChangeSignal().notify(this,&VolumetricFogRTManager::ResizeRT); + + if (mTargetScale < 1) + mTargetScale = 1; + + mWidth = mFloor(mPlatformWindow->getClientExtent().x / mTargetScale); + mHeight = mPlatformWindow->getClientExtent().y; + mFullScreen = mPlatformWindow->isFullscreen(); + if (!mFullScreen) + mHeight -= 20;//subtract caption bar from rendertarget size. + mHeight = mFloor(mHeight / mTargetScale); + + mDepthBuffer = GFXTexHandle(mWidth, mHeight, GFXFormatR32F, + &GFXDefaultRenderTargetProfile, avar("%s() - mDepthBuffer (line %d)", __FUNCTION__, __LINE__)); + if (!mDepthBuffer.isValid()) + { + Con::errorf("VolumetricFogRTManager Fatal Error: Unable to create Depthbuffer"); + return false; + } + if (!mDepthTarget.registerWithName("volfogdepth")) + { + Con::errorf("VolumetricFogRTManager Fatal Error : Unable to register Depthbuffer"); + return false; + } + mDepthTarget.setTexture(mDepthBuffer); + + mFrontBuffer = GFXTexHandle(mWidth, mHeight, GFXFormatR32F, + &GFXDefaultRenderTargetProfile, avar("%s() - mFrontBuffer (line %d)", __FUNCTION__, __LINE__)); + if (!mFrontBuffer.isValid()) + { + Con::errorf("VolumetricFogRTManager Fatal Error: Unable to create front buffer"); + return false; + } + if (!mFrontTarget.registerWithName("volfogfront")) + { + Con::errorf("VolumetricFogRTManager Fatal Error : Unable to register Frontbuffer"); + return false; + } + + mFrontTarget.setTexture(mFrontBuffer); + + Con::setVariable("$VolumetricFog::density", "0.0"); + + mIsInitialized = true; + + return true; +} + +U32 VolumetricFogRTManager::IncFogObjects() +{ + mNumFogObjects++; + return mNumFogObjects; +} + +U32 VolumetricFogRTManager::DecFogObjects() +{ + if (mNumFogObjects > 0) + mNumFogObjects--; + return mNumFogObjects; +} + +void VolumetricFogRTManager::ResizeRT(PlatformWindow* win,bool resize) +{ + mFogHasAnswered = 0; + smVolumetricFogRTMResizeSignal.trigger(this, true); +} + +void VolumetricFogRTManager::FogAnswered() +{ + mFogHasAnswered++; + if (mFogHasAnswered == mNumFogObjects) + { + if (Resize()) + smVolumetricFogRTMResizeSignal.trigger(this, false); + else + Con::errorf("VolumetricFogRTManager::FogAnswered - Error resizing rendertargets!"); + } +} + +bool VolumetricFogRTManager::Resize() +{ + if (mTargetScale < 1) + mTargetScale = 1; + mWidth = mFloor(mPlatformWindow->getClientExtent().x / mTargetScale); + mHeight = mPlatformWindow->getClientExtent().y; + + if (!mPlatformWindow->isFullscreen()) + mHeight -= 20;//subtract caption bar from rendertarget size. + mHeight = mFloor(mHeight / mTargetScale); + + if (mWidth < 16 || mHeight < 16) + return false; + + if (mFrontTarget.isRegistered()) + mFrontTarget.setTexture(NULL); + + if (mDepthTarget.isRegistered()) + mDepthTarget.setTexture(NULL); + + if (mDepthBuffer.isValid()) + mDepthBuffer->kill(); + + if (mFrontBuffer.isValid()) + mFrontBuffer->kill(); + + mFrontBuffer = GFXTexHandle(mWidth, mHeight, GFXFormatR32F, + &GFXDefaultRenderTargetProfile, avar("%s() - mFrontBuffer (line %d)", __FUNCTION__, __LINE__)); + if (!mFrontBuffer.isValid()) + { + Con::errorf("VolumetricFogRTManager::Resize() Fatal Error: Unable to create front buffer"); + return false; + } + mFrontTarget.setTexture(mFrontBuffer); + + mDepthBuffer = GFXTexHandle(mWidth, mHeight, GFXFormatR32F, + &GFXDefaultRenderTargetProfile, avar("%s() - mDepthBuffer (line %d)", __FUNCTION__, __LINE__)); + if (!mDepthBuffer.isValid()) + { + Con::errorf("VolumetricFogRTManager::Resize() Fatal Error: Unable to create Depthbuffer"); + return false; + } + mDepthTarget.setTexture(mDepthBuffer); + return true; +} + +S32 VolumetricFogRTManager::setQuality(U32 Quality) +{ + if (!mIsInitialized) + return (mTargetScale = Quality); + + if (Quality < 1) + Quality = 1; + + if (Quality == mTargetScale) + return mTargetScale; + + mTargetScale = Quality; + + mFogHasAnswered = 0; + smVolumetricFogRTMResizeSignal.trigger(this, true); + + return mTargetScale; +} + +VolumetricFogRTManager* VolumetricFogRTManager::get() +{ + return gVolumetricFogRTManager; +} + +DefineConsoleFunction(SetFogVolumeQuality, S32, (U32 new_quality), , +"@brief Resizes the rendertargets of the Volumetric Fog object.\n" +"@params new_quality new quality for the rendertargets 1 = full size, 2 = halfsize, 3 = 1/3, 4 = 1/4 ...") +{ + if (VFRTM == NULL) + return -1; + return VFRTM->setQuality(new_quality); +} \ No newline at end of file diff --git a/Engine/source/environment/VolumetricFogRTManager.h b/Engine/source/environment/VolumetricFogRTManager.h new file mode 100644 index 000000000..d69bed6bd --- /dev/null +++ b/Engine/source/environment/VolumetricFogRTManager.h @@ -0,0 +1,92 @@ +//----------------------------------------------------------------------------- +// Copyright (c) 2012 GarageGames, LLC +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. +//----------------------------------------------------------------------------- + +#ifndef _VolumetricFogRTManager_H_ +#define _VolumetricFogRTManager_H_ + +#ifndef _SCENEOBJECT_H_ +#include "scene/sceneObject.h" +#endif +#ifndef _MATTEXTURETARGET_H_ +#include "materials/matTextureTarget.h" +#endif +#ifndef _GFXTARGET_H_ +#include "gfx/gfxTarget.h" +#endif +#ifndef _SIGNAL_H_ +#include "core/util/tSignal.h" +#endif + +class VolumetricFogRTManager; + +typedef Signal VolumetricFogRTMResizeSignal; + +#define VFRTM VolumetricFogRTManager::get() + +class VolumetricFogRTManager : public SceneObject +{ + public: + typedef SceneObject Parent; + + protected: + GFXTexHandle mDepthBuffer; + GFXTexHandle mFrontBuffer; + + NamedTexTarget mDepthTarget; + NamedTexTarget mFrontTarget; + + PlatformWindow* mPlatformWindow; + + static S32 mTargetScale; + bool mIsInitialized; + U32 mNumFogObjects; + U32 mFogHasAnswered; + U32 mWidth; + U32 mHeight; + bool mFullScreen; + + void onRemove(); + void onSceneRemove(); + void ResizeRT(PlatformWindow *win, bool resize); + + static VolumetricFogRTMResizeSignal smVolumetricFogRTMResizeSignal; + + public: + VolumetricFogRTManager(); + ~VolumetricFogRTManager(); + static VolumetricFogRTManager *get(); + bool Init(); + bool IsInitialized() { return mIsInitialized; } + static void consoleInit(); + static VolumetricFogRTMResizeSignal& getVolumetricFogRTMResizeSignal() { return smVolumetricFogRTMResizeSignal; } + void FogAnswered(); + S32 setQuality(U32 Quality); + bool Resize(); + U32 IncFogObjects(); + U32 DecFogObjects(); + + DECLARE_CONOBJECT(VolumetricFogRTManager); +}; + +extern VolumetricFogRTManager* gVolumetricFogRTManager; + +#endif \ No newline at end of file From d6226a71caab12be53a0854d8816b0742d243c4d Mon Sep 17 00:00:00 2001 From: Robert MacGregor Date: Wed, 2 Dec 2015 01:47:29 -0500 Subject: [PATCH 052/109] Fix NULL pointer deref crashes in WorldEditor::selectObject & WorldEditor::unSelectObject --- Engine/source/gui/worldEditor/worldEditor.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Engine/source/gui/worldEditor/worldEditor.cpp b/Engine/source/gui/worldEditor/worldEditor.cpp index 0273ffeb4..6065b233e 100644 --- a/Engine/source/gui/worldEditor/worldEditor.cpp +++ b/Engine/source/gui/worldEditor/worldEditor.cpp @@ -2810,7 +2810,7 @@ void WorldEditor::clearSelection() void WorldEditor::selectObject( SimObject *obj ) { - if ( mSelectionLocked || !mSelected ) + if ( mSelectionLocked || !mSelected || !obj ) return; // Don't check isSelectionEnabled of SceneObjects here as we @@ -2833,7 +2833,7 @@ void WorldEditor::selectObject( const char* obj ) void WorldEditor::unselectObject( SimObject *obj ) { - if ( mSelectionLocked || !mSelected ) + if ( mSelectionLocked || !mSelected || !obj ) return; if ( !objClassIgnored( obj ) && mSelected->objInSet( obj ) ) From f06db00255c85f4ea7f2d230570bc58c3f1c1a35 Mon Sep 17 00:00:00 2001 From: Azaezel Date: Thu, 3 Dec 2015 18:34:53 -0600 Subject: [PATCH 053/109] dynamic_cast check for regeneration for paranoias sake + an alias method. --- Engine/source/navigation/navMesh.cpp | 24 +++++++++++++++++++++++- 1 file changed, 23 insertions(+), 1 deletion(-) diff --git a/Engine/source/navigation/navMesh.cpp b/Engine/source/navigation/navMesh.cpp index 738348d2f..74d71dc77 100644 --- a/Engine/source/navigation/navMesh.cpp +++ b/Engine/source/navigation/navMesh.cpp @@ -112,7 +112,7 @@ DefineConsoleFunction(NavMeshUpdateAll, void, (S32 objid, bool remove), (0, fals SimSet *set = NavMesh::getServerSet(); for(U32 i = 0; i < set->size(); i++) { - NavMesh *m = static_cast(set->at(i)); + NavMesh *m = dynamic_cast(set->at(i)); if (m) { m->cancelBuild(); @@ -123,6 +123,28 @@ DefineConsoleFunction(NavMeshUpdateAll, void, (S32 objid, bool remove), (0, fals obj->enableCollision(); } +DefineConsoleFunction(NavMeshUpdateAroundObject, void, (S32 objid, bool remove), (0, false), + "@brief Update all NavMesh tiles that intersect the given object's world box.") +{ + SceneObject *obj; + if (!Sim::findObject(objid, obj)) + return; + if (remove) + obj->disableCollision(); + SimSet *set = NavMesh::getServerSet(); + for (U32 i = 0; i < set->size(); i++) + { + NavMesh *m = dynamic_cast(set->at(i)); + if (m) + { + m->cancelBuild(); + m->buildTiles(obj->getWorldBox()); + } + } + if (remove) + obj->enableCollision(); +} + DefineConsoleFunction(NavMeshUpdateOne, void, (S32 meshid, S32 objid, bool remove), (0, 0, false), "@brief Update all tiles in a given NavMesh that intersect the given object's world box.") { From ac7d6e66912425b259685aad7584110e4aaea516 Mon Sep 17 00:00:00 2001 From: Marc Chapman Date: Sun, 13 Dec 2015 03:33:39 +0000 Subject: [PATCH 054/109] Updated paths for collada tdictionary.h --- Engine/source/ts/collada/colladaAppMesh.h | 2 +- Engine/source/ts/collada/colladaAppNode.h | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Engine/source/ts/collada/colladaAppMesh.h b/Engine/source/ts/collada/colladaAppMesh.h index 17ac1cad1..72c16e56d 100644 --- a/Engine/source/ts/collada/colladaAppMesh.h +++ b/Engine/source/ts/collada/colladaAppMesh.h @@ -24,7 +24,7 @@ #define _COLLADA_APPMESH_H_ #ifndef _TDICTIONARY_H_ -#include "core/tDictionary.h" +#include "core/util/tDictionary.h" #endif #ifndef _APPMESH_H_ #include "ts/loader/appMesh.h" diff --git a/Engine/source/ts/collada/colladaAppNode.h b/Engine/source/ts/collada/colladaAppNode.h index 472ec505c..f633e7be6 100644 --- a/Engine/source/ts/collada/colladaAppNode.h +++ b/Engine/source/ts/collada/colladaAppNode.h @@ -24,7 +24,7 @@ #define _COLLADA_APPNODE_H_ #ifndef _TDICTIONARY_H_ -#include "core/tDictionary.h" +#include "core/util/tDictionary.h" #endif #ifndef _APPNODE_H_ #include "ts/loader/appNode.h" From e3b228db8bf7c34f205724a55505f9294b2c41bf Mon Sep 17 00:00:00 2001 From: rextimmy Date: Mon, 28 Dec 2015 09:17:14 +1000 Subject: [PATCH 055/109] Fix for OpenGL/D3D11 bottom border offset --- Engine/source/gfx/gfxDrawUtil.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Engine/source/gfx/gfxDrawUtil.cpp b/Engine/source/gfx/gfxDrawUtil.cpp index 1e677685e..34b1c8872 100644 --- a/Engine/source/gfx/gfxDrawUtil.cpp +++ b/Engine/source/gfx/gfxDrawUtil.cpp @@ -526,10 +526,10 @@ void GFXDrawUtil::drawRectFill( const Point2F &upperLeft, const Point2F &lowerRi F32 ulOffset = 0.5f - mDevice->getFillConventionOffset(); - verts[0].point.set( upperLeft.x+nw.x+ulOffset, upperLeft.y+nw.y+ulOffset, 0.0f ); - verts[1].point.set( lowerRight.x+ne.x, upperLeft.y+ne.y+ulOffset, 0.0f ); - verts[2].point.set( upperLeft.x-ne.x+ulOffset, lowerRight.y-ne.y, 0.0f ); - verts[3].point.set( lowerRight.x-nw.x, lowerRight.y-nw.y, 0.0f ); + verts[0].point.set( upperLeft.x + nw.x + ulOffset, upperLeft.y + nw.y + ulOffset, 0.0f); + verts[1].point.set( lowerRight.x + ne.x + ulOffset, upperLeft.y + ne.y + ulOffset, 0.0f); + verts[2].point.set( upperLeft.x - ne.x + ulOffset, lowerRight.y - ne.y + ulOffset, 0.0f); + verts[3].point.set( lowerRight.x - nw.x + ulOffset, lowerRight.y - nw.y + ulOffset, 0.0f); for (S32 i=0; i<4; i++) verts[i].color = color; From c2da755dc26b5d4e904785cdb72d9f4ca7a05c98 Mon Sep 17 00:00:00 2001 From: Areloch Date: Fri, 8 Jan 2016 00:19:11 -0600 Subject: [PATCH 056/109] Fix for the directory scan for modules so it doesn't trim off characters in the path. Resubmitted to clear the excess history entries. --- Engine/source/platformWin32/winFileio.cpp | 23 +++++++++++++++++------ 1 file changed, 17 insertions(+), 6 deletions(-) diff --git a/Engine/source/platformWin32/winFileio.cpp b/Engine/source/platformWin32/winFileio.cpp index d5fdde104..85f8676a3 100644 --- a/Engine/source/platformWin32/winFileio.cpp +++ b/Engine/source/platformWin32/winFileio.cpp @@ -1321,6 +1321,7 @@ static bool recurseDumpDirectories(const char *basePath, const char *subPath, Ve dsize_t subtrLen = subPath ? dStrlen(subPath) : 0; char trail = trLen > 0 ? basePath[ trLen - 1 ] : '\0'; char subTrail = subtrLen > 0 ? subPath[ subtrLen - 1 ] : '\0'; + char subLead = subtrLen > 0 ? subPath[0] : '\0'; if( trail == '/' ) { @@ -1380,13 +1381,23 @@ static bool recurseDumpDirectories(const char *basePath, const char *subPath, Ve { if( ( subPath && ( dStrncmp( subPath, "", 1 ) != 0 ) ) ) { - char szPath [ 1024 ]; - dMemset( szPath, 0, 1024 ); - if( trail != '/' ) - dSprintf( szPath, 1024, "%s%s", basePath, subPath ); + char szPath[1024]; + dMemset(szPath, 0, 1024); + if (trail == '/') + { + if (subLead == '/') + dSprintf(szPath, 1024, "%s%s", basePath, &subPath[1]); + else + dSprintf(szPath, 1024, "%s%s", basePath, subPath); + } else - dSprintf( szPath, 1024, "%s%s", basePath, &subPath[1] ); - directoryVector.push_back( StringTable->insert( szPath ) ); + { + if (subLead == '/') + dSprintf(szPath, 1024, "%s%s", basePath, subPath); + else + dSprintf(szPath, 1024, "%s/%s", basePath, subPath); + } + directoryVector.push_back(StringTable->insert(szPath)); } else directoryVector.push_back( StringTable->insert( basePath ) ); From 4c17d4bb499ad78de05c49d0c609669e21a193de Mon Sep 17 00:00:00 2001 From: Cameron Porter Date: Sat, 9 Jan 2016 00:37:41 -0600 Subject: [PATCH 057/109] Fix case sensitivity and Platform::fileDelete for linux and OSX. Correct a couple of warnings and errors preventing builds on linux. --- Engine/source/T3D/assets/ShapeAsset.h | 2 +- Engine/source/core/stream/stream.cpp | 2 +- Engine/source/gfx/gfxDevice.cpp | 2 +- Engine/source/math/mathUtils.cpp | 2 +- Engine/source/persistence/taml/fsTinyXml.cpp | 6 +++--- Engine/source/persistence/taml/fsTinyXml.h | 4 ++-- Engine/source/persistence/taml/xml/tamlXmlParser.h | 4 ++-- Engine/source/persistence/taml/xml/tamlXmlReader.cpp | 2 +- Engine/source/persistence/taml/xml/tamlXmlWriter.cpp | 2 +- Engine/source/platformMac/macCarbFileio.mm | 5 +++++ Engine/source/platformX86UNIX/x86UNIXFileio.cpp | 7 ++++++- 11 files changed, 24 insertions(+), 14 deletions(-) diff --git a/Engine/source/T3D/assets/ShapeAsset.h b/Engine/source/T3D/assets/ShapeAsset.h index 81b716d08..7c87cf8de 100644 --- a/Engine/source/T3D/assets/ShapeAsset.h +++ b/Engine/source/T3D/assets/ShapeAsset.h @@ -39,7 +39,7 @@ #endif #ifndef _TSSHAPE_H_ -#include "ts/TSShape.h" +#include "ts/tsShape.h" #endif #ifndef __RESOURCE_H__ #include "core/resource.h" diff --git a/Engine/source/core/stream/stream.cpp b/Engine/source/core/stream/stream.cpp index da1b1dc82..8a7fb2b40 100644 --- a/Engine/source/core/stream/stream.cpp +++ b/Engine/source/core/stream/stream.cpp @@ -128,7 +128,7 @@ bool Stream::writeFormattedBuffer(const char *format, ...) char buffer[4096]; va_list args; va_start(args, format); - const S32 length = vsprintf(buffer, format, args); + const S32 length = dVsprintf(buffer, sizeof(buffer), format, args); // Sanity! AssertFatal(length <= sizeof(buffer), "writeFormattedBuffer - String format exceeded buffer size. This will cause corruption."); diff --git a/Engine/source/gfx/gfxDevice.cpp b/Engine/source/gfx/gfxDevice.cpp index c72c109d0..3f63fb884 100644 --- a/Engine/source/gfx/gfxDevice.cpp +++ b/Engine/source/gfx/gfxDevice.cpp @@ -148,7 +148,7 @@ GFXDevice::GFXDevice() mGlobalAmbientColor = ColorF(0.0f, 0.0f, 0.0f, 1.0f); mLightMaterialDirty = false; - dMemset(&mCurrentLightMaterial, NULL, sizeof(GFXLightMaterial)); + dMemset(&mCurrentLightMaterial, 0, sizeof(GFXLightMaterial)); // State block mStateBlockDirty = false; diff --git a/Engine/source/math/mathUtils.cpp b/Engine/source/math/mathUtils.cpp index f6086c5e4..5ffd9a870 100644 --- a/Engine/source/math/mathUtils.cpp +++ b/Engine/source/math/mathUtils.cpp @@ -1847,7 +1847,7 @@ U32 extrudePolygonEdgesFromPoint( const Point3F* vertices, U32 numVertices, cons //----------------------------------------------------------------------------- -void MathUtils::mBuildHull2D(const Vector _inPoints, Vector &hullPoints) +void mBuildHull2D(const Vector _inPoints, Vector &hullPoints) { /// Andrew's monotone chain convex hull algorithm implementation diff --git a/Engine/source/persistence/taml/fsTinyXml.cpp b/Engine/source/persistence/taml/fsTinyXml.cpp index 1fb97398b..441169742 100644 --- a/Engine/source/persistence/taml/fsTinyXml.cpp +++ b/Engine/source/persistence/taml/fsTinyXml.cpp @@ -38,7 +38,7 @@ bool fsTiXmlDocument::LoadFile( const char * pFilename, TiXmlEncoding encoding ) #endif // File open for read? - if ( !stream.open( filenameBuffer, Torque::FS::File::AccessMode::Read ) ) + if ( !stream.open( filenameBuffer, Torque::FS::File::Read ) ) { // No, so warn. Con::warnf("TamlXmlParser::parse() - Could not open filename '%s' for parse.", filenameBuffer ); @@ -67,7 +67,7 @@ bool fsTiXmlDocument::SaveFile( const char * pFilename ) const FileStream stream; // File opened? - if ( !stream.open( filenameBuffer, Torque::FS::File::AccessMode::Write ) ) + if ( !stream.open( filenameBuffer, Torque::FS::File::Write ) ) { // No, so warn. Con::warnf("Taml::writeFile() - Could not open filename '%s' for write.", filenameBuffer ); @@ -744,4 +744,4 @@ return 0; // All is well. return p; } -*/ \ No newline at end of file +*/ diff --git a/Engine/source/persistence/taml/fsTinyXml.h b/Engine/source/persistence/taml/fsTinyXml.h index f2fb14129..864abec09 100644 --- a/Engine/source/persistence/taml/fsTinyXml.h +++ b/Engine/source/persistence/taml/fsTinyXml.h @@ -25,7 +25,7 @@ #ifndef TINYXML_INCLUDED -#include "tinyXML/tinyxml.h" +#include "tinyxml/tinyxml.h" #endif #include "platform/platform.h" @@ -245,4 +245,4 @@ static bool AttemptPrintTiNode(class fsTiXmlDocument* node, FileStream& stream, } return false; } -#endif //_FSTINYXML_H_ \ No newline at end of file +#endif //_FSTINYXML_H_ diff --git a/Engine/source/persistence/taml/xml/tamlXmlParser.h b/Engine/source/persistence/taml/xml/tamlXmlParser.h index 85b34079d..28b51a472 100644 --- a/Engine/source/persistence/taml/xml/tamlXmlParser.h +++ b/Engine/source/persistence/taml/xml/tamlXmlParser.h @@ -28,7 +28,7 @@ #endif #ifndef TINYXML_INCLUDED -#include "tinyXML/tinyxml.h" +#include "tinyxml/tinyxml.h" #endif //----------------------------------------------------------------------------- @@ -54,4 +54,4 @@ private: bool mDocumentDirty; }; -#endif // _TAML_XMLPARSER_H_ \ No newline at end of file +#endif // _TAML_XMLPARSER_H_ diff --git a/Engine/source/persistence/taml/xml/tamlXmlReader.cpp b/Engine/source/persistence/taml/xml/tamlXmlReader.cpp index 04b43a5d2..aa8be618f 100644 --- a/Engine/source/persistence/taml/xml/tamlXmlReader.cpp +++ b/Engine/source/persistence/taml/xml/tamlXmlReader.cpp @@ -24,7 +24,7 @@ // Debug Profiling. #include "platform/profiler.h" -#include "persistence/taml/fsTinyxml.h" +#include "persistence/taml/fsTinyXml.h" //----------------------------------------------------------------------------- diff --git a/Engine/source/persistence/taml/xml/tamlXmlWriter.cpp b/Engine/source/persistence/taml/xml/tamlXmlWriter.cpp index 4ffad7223..e8481cb87 100644 --- a/Engine/source/persistence/taml/xml/tamlXmlWriter.cpp +++ b/Engine/source/persistence/taml/xml/tamlXmlWriter.cpp @@ -24,7 +24,7 @@ // Debug Profiling. #include "platform/profiler.h" -#include "persistence/taml/fsTinyxml.h" +#include "persistence/taml/fsTinyXml.h" //----------------------------------------------------------------------------- diff --git a/Engine/source/platformMac/macCarbFileio.mm b/Engine/source/platformMac/macCarbFileio.mm index 7a913986e..a1961d710 100644 --- a/Engine/source/platformMac/macCarbFileio.mm +++ b/Engine/source/platformMac/macCarbFileio.mm @@ -724,6 +724,11 @@ bool Platform::hasSubDirectory(const char *path) return false; // either this dir had no subdirectories, or they were all on the exclude list. } + bool Platform::fileDelete(const char * name) + { + return dFileDelete(name); + } + //----------------------------------------------------------------------------- bool recurseDumpDirectories(const char *basePath, const char *path, Vector &directoryVector, S32 depth, bool noBasePath) { diff --git a/Engine/source/platformX86UNIX/x86UNIXFileio.cpp b/Engine/source/platformX86UNIX/x86UNIXFileio.cpp index 22c40a187..6c2dd6955 100644 --- a/Engine/source/platformX86UNIX/x86UNIXFileio.cpp +++ b/Engine/source/platformX86UNIX/x86UNIXFileio.cpp @@ -325,7 +325,7 @@ bool dPathCopy(const char *fromName, const char *toName, bool nooverwrite) if (modType == TOUCH) return(utime(prefPathName, 0) != -1); else if (modType == DELETE) - return (remove(prefPathName) != -1); + return (remove(prefPathName) == 0); else AssertFatal(false, "Unknown File Mod type"); return false; @@ -1140,6 +1140,11 @@ bool dPathCopy(const char *fromName, const char *toName, bool nooverwrite) return false; } + bool Platform::fileDelete(const char * name) + { + return ModifyFile(name, DELETE); + } + static bool recurseDumpDirectories(const char *basePath, const char *subPath, Vector &directoryVector, S32 currentDepth, S32 recurseDepth, bool noBasePath) { char Path[1024]; From 62506214d0d29ebda1de1446dd8ea23da1d763b3 Mon Sep 17 00:00:00 2001 From: Azaezel Date: Mon, 11 Jan 2016 22:23:13 -0600 Subject: [PATCH 058/109] footstep and impact enum extension support Removes hardcoded case statements in favor of an offset-driven approach. --- Engine/source/T3D/player.cpp | 51 ++++-------------------------------- Engine/source/T3D/player.h | 3 ++- 2 files changed, 7 insertions(+), 47 deletions(-) diff --git a/Engine/source/T3D/player.cpp b/Engine/source/T3D/player.cpp index 3c258c374..32da4703d 100644 --- a/Engine/source/T3D/player.cpp +++ b/Engine/source/T3D/player.cpp @@ -6878,26 +6878,8 @@ void Player::playFootstepSound( bool triggeredLeft, Material* contactMaterial, S else if( contactObject && contactObject->getTypeMask() & VehicleObjectType ) sound = 2; - switch ( sound ) - { - case 0: // Soft - SFX->playOnce( mDataBlock->sound[PlayerData::FootSoft], &footMat ); - break; - case 1: // Hard - SFX->playOnce( mDataBlock->sound[PlayerData::FootHard], &footMat ); - break; - case 2: // Metal - SFX->playOnce( mDataBlock->sound[PlayerData::FootMetal], &footMat ); - break; - case 3: // Snow - SFX->playOnce( mDataBlock->sound[PlayerData::FootSnow], &footMat ); - break; - /* - default: //Hard - SFX->playOnce( mDataBlock->sound[PlayerData::FootHard], &footMat ); - break; - */ - } + if (sound>=0) + SFX->playOnce(mDataBlock->sound[sound], &footMat); } } @@ -6922,36 +6904,13 @@ void Player:: playImpactSound() else { S32 sound = -1; - if( material && material->mImpactSoundId ) + if( material && (material->mImpactSoundId>=0) ) sound = material->mImpactSoundId; else if( rInfo.object->getTypeMask() & VehicleObjectType ) sound = 2; // Play metal; - switch( sound ) - { - case 0: - //Soft - SFX->playOnce( mDataBlock->sound[ PlayerData::ImpactSoft ], &getTransform() ); - break; - case 1: - //Hard - SFX->playOnce( mDataBlock->sound[ PlayerData::ImpactHard ], &getTransform() ); - break; - case 2: - //Metal - SFX->playOnce( mDataBlock->sound[ PlayerData::ImpactMetal ], &getTransform() ); - break; - case 3: - //Snow - SFX->playOnce( mDataBlock->sound[ PlayerData::ImpactSnow ], &getTransform() ); - break; - /* - default: - //Hard - alxPlay(mDataBlock->sound[PlayerData::ImpactHard], &getTransform()); - break; - */ - } + if (sound >= 0) + SFX->playOnce(mDataBlock->sound[PlayerData::ImpactStart + sound], &getTransform()); } } } diff --git a/Engine/source/T3D/player.h b/Engine/source/T3D/player.h index eb1a39443..b2947c4dc 100644 --- a/Engine/source/T3D/player.h +++ b/Engine/source/T3D/player.h @@ -196,7 +196,8 @@ struct PlayerData: public ShapeBaseData { FootBubbles, MoveBubbles, WaterBreath, - ImpactSoft, + ImpactStart, + ImpactSoft = ImpactStart, ImpactHard, ImpactMetal, ImpactSnow, From c60be9a17eb31172a89fb604a36659d93b3c69f5 Mon Sep 17 00:00:00 2001 From: Azaezel Date: Wed, 13 Jan 2016 15:08:21 -0600 Subject: [PATCH 059/109] suggested revisions --- Engine/source/T3D/player.cpp | 4 ++-- Engine/source/T3D/player.h | 1 + 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/Engine/source/T3D/player.cpp b/Engine/source/T3D/player.cpp index 32da4703d..21ba1af7a 100644 --- a/Engine/source/T3D/player.cpp +++ b/Engine/source/T3D/player.cpp @@ -6873,7 +6873,7 @@ void Player::playFootstepSound( bool triggeredLeft, Material* contactMaterial, S // Play default sound. S32 sound = -1; - if( contactMaterial && contactMaterial->mFootstepSoundId != -1 ) + if (contactMaterial && (contactMaterial->mImpactSoundId>-1 && contactMaterial->mImpactSoundIdmFootstepSoundId; else if( contactObject && contactObject->getTypeMask() & VehicleObjectType ) sound = 2; @@ -6904,7 +6904,7 @@ void Player:: playImpactSound() else { S32 sound = -1; - if( material && (material->mImpactSoundId>=0) ) + if (material && (material->mImpactSoundId>-1 && material->mImpactSoundIdmImpactSoundId; else if( rInfo.object->getTypeMask() & VehicleObjectType ) sound = 2; // Play metal; diff --git a/Engine/source/T3D/player.h b/Engine/source/T3D/player.h index b2947c4dc..4ffd6c95d 100644 --- a/Engine/source/T3D/player.h +++ b/Engine/source/T3D/player.h @@ -190,6 +190,7 @@ struct PlayerData: public ShapeBaseData { FootHard, FootMetal, FootSnow, + MaxSoundOffsets, FootShallowSplash, FootWading, FootUnderWater, From cae97cac37a1aed5849d27eb7a3b48b35000740a Mon Sep 17 00:00:00 2001 From: Anis Date: Thu, 14 Jan 2016 23:51:35 +0100 Subject: [PATCH 060/109] Glow buffer graphic corruption fix on OpenGL. Caused by a wrong target size. (probably it was ok on the very old OpenGL 1.5 version) Before fix, wrong behaviour: http://goo.gl/dik7Ia After fix, all right: http://goo.gl/IsrckM --- Engine/source/renderInstance/renderTexTargetBinManager.cpp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/Engine/source/renderInstance/renderTexTargetBinManager.cpp b/Engine/source/renderInstance/renderTexTargetBinManager.cpp index c26231152..5353f4566 100644 --- a/Engine/source/renderInstance/renderTexTargetBinManager.cpp +++ b/Engine/source/renderInstance/renderTexTargetBinManager.cpp @@ -107,8 +107,7 @@ void RenderTexTargetBinManager::initPersistFields() bool RenderTexTargetBinManager::setTargetSize(const Point2I &newTargetSize) { - if( GFX->getAdapterType() != OpenGL && // Targets need to match up exactly in size on OpenGL. - mTargetSize.x >= newTargetSize.x && + if( mTargetSize.x >= newTargetSize.x && mTargetSize.y >= newTargetSize.y ) return true; From 58a604d363cac4cabf9148d402e3a17c44e28008 Mon Sep 17 00:00:00 2001 From: Anis Date: Mon, 18 Jan 2016 05:49:05 +0100 Subject: [PATCH 061/109] Update gfxGLCircularVolatileBuffer.h --- Engine/source/gfx/gl/gfxGLCircularVolatileBuffer.h | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/Engine/source/gfx/gl/gfxGLCircularVolatileBuffer.h b/Engine/source/gfx/gl/gfxGLCircularVolatileBuffer.h index c291cb229..6d7d0e4b1 100644 --- a/Engine/source/gfx/gl/gfxGLCircularVolatileBuffer.h +++ b/Engine/source/gfx/gl/gfxGLCircularVolatileBuffer.h @@ -143,6 +143,11 @@ public: init(); } + ~GLCircularVolatileBuffer() + { + glDeleteBuffers(1, &mBufferName); + } + void init() { glGenBuffers(1, &mBufferName); @@ -290,4 +295,4 @@ protected: }; -#endif \ No newline at end of file +#endif From 07282822870c6a86de7da7f42a1de4e85c15c402 Mon Sep 17 00:00:00 2001 From: Anis Date: Mon, 18 Jan 2016 05:55:36 +0100 Subject: [PATCH 062/109] Update gfxGLTextureTarget.cpp --- Engine/source/gfx/gl/gfxGLTextureTarget.cpp | 23 ++++++++++++++++++++- 1 file changed, 22 insertions(+), 1 deletion(-) diff --git a/Engine/source/gfx/gl/gfxGLTextureTarget.cpp b/Engine/source/gfx/gl/gfxGLTextureTarget.cpp index 1569b9a85..202265107 100644 --- a/Engine/source/gfx/gl/gfxGLTextureTarget.cpp +++ b/Engine/source/gfx/gl/gfxGLTextureTarget.cpp @@ -163,6 +163,10 @@ void _GFXGLTextureTargetFBOImpl::applyState() PRESERVE_FRAMEBUFFER(); glBindFramebuffer(GL_FRAMEBUFFER, mFramebuffer); + bool drawbufs[16]; + int bufsize = 0; + for (int i = 0; i < 16; i++) + drawbufs[i] = false; bool hasColor = false; for(int i = 0; i < GFXGL->getNumRenderTargets(); ++i) { @@ -200,6 +204,20 @@ void _GFXGLTextureTargetFBOImpl::applyState() glFramebufferTexture2D(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_TEXTURE_2D, 0, 0); } + GLenum *buf = new GLenum[bufsize]; + int count = 0; + for (int i = 0; i < bufsize; i++) + { + if (drawbufs[i]) + { + buf[count] = GL_COLOR_ATTACHMENT0 + i; + count++; + } + } + + glDrawBuffers(bufsize, buf); + + delete[] buf; CHECK_FRAMEBUFFER_STATUS(); } @@ -260,7 +278,10 @@ GFXGLTextureTarget::GFXGLTextureTarget() : mCopyFboSrc(0), mCopyFboDst(0) GFXGLTextureTarget::~GFXGLTextureTarget() { - GFXTextureManager::removeEventDelegate( this, &GFXGLTextureTarget::_onTextureEvent ); + GFXTextureManager::removeEventDelegate(this, &GFXGLTextureTarget::_onTextureEvent); + + glDeleteFramebuffers(1, &mCopyFboSrc); + glDeleteFramebuffers(1, &mCopyFboDst); } const Point2I GFXGLTextureTarget::getSize() From c3ef59e39c9784812810d7c24c17b775a91f0e48 Mon Sep 17 00:00:00 2001 From: Anis Date: Mon, 18 Jan 2016 05:55:48 +0100 Subject: [PATCH 063/109] Update gfxGLWindowTarget.cpp --- Engine/source/gfx/gl/gfxGLWindowTarget.cpp | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/Engine/source/gfx/gl/gfxGLWindowTarget.cpp b/Engine/source/gfx/gl/gfxGLWindowTarget.cpp index c02ff1bc3..5f8808cae 100644 --- a/Engine/source/gfx/gl/gfxGLWindowTarget.cpp +++ b/Engine/source/gfx/gl/gfxGLWindowTarget.cpp @@ -42,6 +42,14 @@ GFXGLWindowTarget::GFXGLWindowTarget(PlatformWindow *win, GFXDevice *d) win->appEvent.notify(this, &GFXGLWindowTarget::_onAppSignal); } +GFXGLWindowTarget::~GFXGLWindowTarget() +{ + if(glIsFramebuffer(mCopyFBO)) + { + glDeleteFramebuffers(1, &mCopyFBO); + } +} + void GFXGLWindowTarget::resetMode() { if(mWindow->getVideoMode().fullScreen != mWindow->isFullscreen()) @@ -49,6 +57,7 @@ void GFXGLWindowTarget::resetMode() _teardownCurrentMode(); _setupNewMode(); } + GFX->beginReset(); } void GFXGLWindowTarget::_onAppSignal(WindowId wnd, S32 event) From 500a237892db6fa6a445aa646815f20cec7ccbfa Mon Sep 17 00:00:00 2001 From: Anis Date: Mon, 18 Jan 2016 05:56:00 +0100 Subject: [PATCH 064/109] Update gfxGLWindowTarget.h --- Engine/source/gfx/gl/gfxGLWindowTarget.h | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/Engine/source/gfx/gl/gfxGLWindowTarget.h b/Engine/source/gfx/gl/gfxGLWindowTarget.h index 4baa6a53f..af368e192 100644 --- a/Engine/source/gfx/gl/gfxGLWindowTarget.h +++ b/Engine/source/gfx/gl/gfxGLWindowTarget.h @@ -30,6 +30,8 @@ class GFXGLWindowTarget : public GFXWindowTarget public: GFXGLWindowTarget(PlatformWindow *win, GFXDevice *d); + ~GFXGLWindowTarget(); + const Point2I getSize() { return mWindow->getClientExtent(); @@ -64,4 +66,4 @@ private: void _WindowPresent(); }; -#endif \ No newline at end of file +#endif From ca31ef3f1aef7c3f0643246c788beeb980474b43 Mon Sep 17 00:00:00 2001 From: Anis Date: Mon, 18 Jan 2016 06:15:07 +0100 Subject: [PATCH 065/109] Update gfxGLWindowTarget.cpp --- Engine/source/gfx/gl/gfxGLWindowTarget.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/Engine/source/gfx/gl/gfxGLWindowTarget.cpp b/Engine/source/gfx/gl/gfxGLWindowTarget.cpp index 5f8808cae..c00506835 100644 --- a/Engine/source/gfx/gl/gfxGLWindowTarget.cpp +++ b/Engine/source/gfx/gl/gfxGLWindowTarget.cpp @@ -57,7 +57,6 @@ void GFXGLWindowTarget::resetMode() _teardownCurrentMode(); _setupNewMode(); } - GFX->beginReset(); } void GFXGLWindowTarget::_onAppSignal(WindowId wnd, S32 event) From e2d789e87de69d1b525763ca99aa5daef15b8e74 Mon Sep 17 00:00:00 2001 From: Anis Date: Mon, 18 Jan 2016 06:17:20 +0100 Subject: [PATCH 066/109] Update gfxGLTextureTarget.cpp --- Engine/source/gfx/gl/gfxGLTextureTarget.cpp | 18 ------------------ 1 file changed, 18 deletions(-) diff --git a/Engine/source/gfx/gl/gfxGLTextureTarget.cpp b/Engine/source/gfx/gl/gfxGLTextureTarget.cpp index 202265107..ddb308adc 100644 --- a/Engine/source/gfx/gl/gfxGLTextureTarget.cpp +++ b/Engine/source/gfx/gl/gfxGLTextureTarget.cpp @@ -163,10 +163,6 @@ void _GFXGLTextureTargetFBOImpl::applyState() PRESERVE_FRAMEBUFFER(); glBindFramebuffer(GL_FRAMEBUFFER, mFramebuffer); - bool drawbufs[16]; - int bufsize = 0; - for (int i = 0; i < 16; i++) - drawbufs[i] = false; bool hasColor = false; for(int i = 0; i < GFXGL->getNumRenderTargets(); ++i) { @@ -204,20 +200,6 @@ void _GFXGLTextureTargetFBOImpl::applyState() glFramebufferTexture2D(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_TEXTURE_2D, 0, 0); } - GLenum *buf = new GLenum[bufsize]; - int count = 0; - for (int i = 0; i < bufsize; i++) - { - if (drawbufs[i]) - { - buf[count] = GL_COLOR_ATTACHMENT0 + i; - count++; - } - } - - glDrawBuffers(bufsize, buf); - - delete[] buf; CHECK_FRAMEBUFFER_STATUS(); } From 23c4b52e1f69fbec2594ffde6bd2952e55bb2e3b Mon Sep 17 00:00:00 2001 From: Azaezel Date: Mon, 18 Jan 2016 00:28:09 -0600 Subject: [PATCH 067/109] courtessy @Lopuska: opengl occlusion query fix --- .../advanced/advancedLightBinManager.cpp | 14 +++------ .../lighting/shadowMap/lightShadowMap.cpp | 31 +++++-------------- .../lighting/shadowMap/lightShadowMap.h | 22 ++++--------- .../lighting/shadowMap/shadowMapPass.cpp | 14 ++++----- 4 files changed, 25 insertions(+), 56 deletions(-) diff --git a/Engine/source/lighting/advanced/advancedLightBinManager.cpp b/Engine/source/lighting/advanced/advancedLightBinManager.cpp index dd71afeb0..98330a5de 100644 --- a/Engine/source/lighting/advanced/advancedLightBinManager.cpp +++ b/Engine/source/lighting/advanced/advancedLightBinManager.cpp @@ -318,6 +318,8 @@ void AdvancedLightBinManager::render( SceneRenderState *state ) const U32 numPrims = curEntry.numPrims; const U32 numVerts = curEntry.vertBuffer->mNumVerts; + ShadowMapParams *lsp = curLightInfo->getExtended(); + // Skip lights which won't affect the scene. if ( !curLightMat || curLightInfo->getBrightness() <= 0.001f ) continue; @@ -329,15 +331,12 @@ void AdvancedLightBinManager::render( SceneRenderState *state ) mShadowManager->setLightShadowMap( curEntry.shadowMap ); mShadowManager->setLightDynamicShadowMap( curEntry.dynamicShadowMap ); - // Let the shadow know we're about to render from it. - if ( curEntry.shadowMap ) - curEntry.shadowMap->preLightRender(); - if ( curEntry.dynamicShadowMap ) curEntry.dynamicShadowMap->preLightRender(); - // Set geometry GFX->setVertexBuffer( curEntry.vertBuffer ); GFX->setPrimitiveBuffer( curEntry.primBuffer ); + lsp->getOcclusionQuery()->begin(); + // Render the material passes while( curLightMat->matInstance->setupPass( state, sgData ) ) { @@ -352,10 +351,7 @@ void AdvancedLightBinManager::render( SceneRenderState *state ) GFX->drawPrimitive(GFXTriangleList, 0, numPrims); } - // Tell it we're done rendering. - if ( curEntry.shadowMap ) - curEntry.shadowMap->postLightRender(); - if ( curEntry.dynamicShadowMap ) curEntry.dynamicShadowMap->postLightRender(); + lsp->getOcclusionQuery()->end(); } // Set NULL for active shadow map (so nothing gets confused) diff --git a/Engine/source/lighting/shadowMap/lightShadowMap.cpp b/Engine/source/lighting/shadowMap/lightShadowMap.cpp index 53a6f4e8d..984f6cbc6 100644 --- a/Engine/source/lighting/shadowMap/lightShadowMap.cpp +++ b/Engine/source/lighting/shadowMap/lightShadowMap.cpp @@ -89,8 +89,6 @@ LightShadowMap::LightShadowMap( LightInfo *light ) mLastUpdate( 0 ), mLastCull( 0 ), mIsViewDependent( false ), - mVizQuery( NULL ), - mWasOccluded( false ), mLastScreenSize( 0.0f ), mLastPriority( 0.0f ), mIsDynamic( false ) @@ -98,9 +96,7 @@ LightShadowMap::LightShadowMap( LightInfo *light ) GFXTextureManager::addEventDelegate( this, &LightShadowMap::_onTextureEvent ); mTarget = GFX->allocRenderToTextureTarget(); - mVizQuery = GFX->createOcclusionQuery(); - - smShadowMaps.push_back(this); + smShadowMaps.push_back( this ); mStaticRefreshTimer = PlatformTimer::create(); mDynamicRefreshTimer = PlatformTimer::create(); } @@ -108,8 +104,9 @@ LightShadowMap::LightShadowMap( LightInfo *light ) LightShadowMap::~LightShadowMap() { mTarget = NULL; - SAFE_DELETE( mVizQuery ); - + SAFE_DELETE(mStaticRefreshTimer); + SAFE_DELETE(mDynamicRefreshTimer); + releaseTextures(); smShadowMaps.remove( this ); @@ -334,23 +331,6 @@ void LightShadowMap::render( RenderPassManager* renderPass, mLastUpdate = Sim::getCurrentTime(); } -void LightShadowMap::preLightRender() -{ - PROFILE_SCOPE( LightShadowMap_prepLightRender ); - - if ( mVizQuery ) - { - mWasOccluded = mVizQuery->getStatus( true ) == GFXOcclusionQuery::Occluded; - mVizQuery->begin(); - } -} - -void LightShadowMap::postLightRender() -{ - if ( mVizQuery ) - mVizQuery->end(); -} - BaseMatInstance* LightShadowMap::getShadowMaterial( BaseMatInstance *inMat ) const { // See if we have an existing material hook. @@ -610,11 +590,14 @@ ShadowMapParams::ShadowMapParams( LightInfo *light ) shadowSoftness = 0.15f; fadeStartDist = 0.0f; lastSplitTerrainOnly = false; + mQuery = GFX->createOcclusionQuery(); + _validate(); } ShadowMapParams::~ShadowMapParams() { + SAFE_DELETE( mQuery ); SAFE_DELETE( mShadowMap ); SAFE_DELETE( mDynamicShadowMap ); } diff --git a/Engine/source/lighting/shadowMap/lightShadowMap.h b/Engine/source/lighting/shadowMap/lightShadowMap.h index 2cbb2ca5e..d96481192 100644 --- a/Engine/source/lighting/shadowMap/lightShadowMap.h +++ b/Engine/source/lighting/shadowMap/lightShadowMap.h @@ -47,6 +47,9 @@ #ifndef _GFXSHADER_H_ #include "gfx/gfxShader.h" #endif +#ifndef _GFXOCCLUSIONQUERY_H_ +#include "gfx/gfxOcclusionQuery.h" +#endif #ifndef _PLATFORM_PLATFORMTIMER_H_ #include "platform/platformTimer.h" #endif @@ -61,7 +64,6 @@ struct SceneData; class GFXShaderConstBuffer; class GFXShaderConstHandle; class GFXShader; -class GFXOcclusionQuery; class LightManager; class RenderPassManager; @@ -169,12 +171,6 @@ public: bool isViewDependent() const { return mIsViewDependent; } - bool wasOccluded() const { return mWasOccluded; } - - void preLightRender(); - - void postLightRender(); - void updatePriority( const SceneRenderState *state, U32 currTimeMs ); F32 getLastScreenSize() const { return mLastScreenSize; } @@ -257,15 +253,6 @@ protected: /// The time this shadow was last culled and prioritized. U32 mLastCull; - /// The shadow occlusion query used when the light is - /// rendered to determine if any pixel of it is visible. - GFXOcclusionQuery *mVizQuery; - - /// If true the light was occluded by geometry the - /// last frame it was updated. - //the last frame. - bool mWasOccluded; - F32 mLastScreenSize; F32 mLastPriority; @@ -325,6 +312,8 @@ public: bool hasCookieTex() const { return cookie.isNotEmpty(); } + GFXOcclusionQuery* getOcclusionQuery() const { return mQuery; } + GFXTextureObject* getCookieTex(); GFXCubemap* getCookieCubeTex(); @@ -339,6 +328,7 @@ protected: /// LightShadowMap *mShadowMap; LightShadowMap *mDynamicShadowMap; + GFXOcclusionQuery* mQuery; LightInfo *mLight; diff --git a/Engine/source/lighting/shadowMap/shadowMapPass.cpp b/Engine/source/lighting/shadowMap/shadowMapPass.cpp index 7c17046e1..dea7305b5 100644 --- a/Engine/source/lighting/shadowMap/shadowMapPass.cpp +++ b/Engine/source/lighting/shadowMap/shadowMapPass.cpp @@ -174,12 +174,12 @@ void ShadowMapPass::render( SceneManager *sceneManager, continue; // --- Static Shadow Map --- - LightShadowMap *lsm = params->getOrCreateShadowMap(); - LightShadowMap *dlsm = params->getOrCreateShadowMap(true); + LightShadowMap *lsm = params->getOrCreateShadowMap(); + LightShadowMap *dlsm = params->getOrCreateShadowMap(true); // First check the visiblity query... if it wasn't // visible skip it. - if (lsm->wasOccluded() || dlsm->wasOccluded()) + if(params->getOcclusionQuery()->getStatus(true) == GFXOcclusionQuery::Occluded) continue; // Any shadow that is visible is counted as being @@ -187,9 +187,9 @@ void ShadowMapPass::render( SceneManager *sceneManager, ++smActiveShadowMaps; // Do a priority update for this shadow. - lsm->updatePriority(diffuseState, currTime); + lsm->updatePriority(diffuseState, currTime); - shadowMaps.push_back(lsm); + shadowMaps.push_back(lsm); // --- Dynamic Shadow Map --- @@ -198,7 +198,7 @@ void ShadowMapPass::render( SceneManager *sceneManager, ++smActiveShadowMaps; // Do a priority update for this shadow. - dlsm->updatePriority(diffuseState, currTime); + dlsm->updatePriority(diffuseState, currTime); shadowMaps.push_back( dlsm ); } @@ -306,4 +306,4 @@ void DynamicShadowRenderPassManager::addInst( RenderInst *inst ) } Parent::addInst(inst); -} \ No newline at end of file +} From 0f173df0d4c81f0e9a66b5724c918b082161b4ed Mon Sep 17 00:00:00 2001 From: Azaezel Date: Thu, 28 Jan 2016 00:42:08 -0600 Subject: [PATCH 068/109] setDetailFromDistance aspect ratio friendly adjustment --- Engine/source/ts/tsShapeInstance.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Engine/source/ts/tsShapeInstance.cpp b/Engine/source/ts/tsShapeInstance.cpp index 821e02dba..03b8c79c3 100644 --- a/Engine/source/ts/tsShapeInstance.cpp +++ b/Engine/source/ts/tsShapeInstance.cpp @@ -593,7 +593,7 @@ S32 TSShapeInstance::setDetailFromDistance( const SceneRenderState *state, F32 s // 4:3 aspect ratio, we've changed the reference value // to 300 to be more compatible with legacy shapes. // - const F32 pixelScale = state->getViewport().extent.y / 300.0f; + const F32 pixelScale = (state->getViewport().extent.x / state->getViewport().extent.y); // This is legacy DTS support for older "multires" based // meshes. The original crossbow weapon uses this. From 7924f056bdfcabb4152c2b55c79695dac8534e27 Mon Sep 17 00:00:00 2001 From: RoundedIcon Date: Mon, 1 Feb 2016 16:58:39 -0700 Subject: [PATCH 069/109] Fix for collision issues with scaled players Players scaled after their creation have collision issues with terrain. Changing this bit of code fixes those issues for downsized players, even when shrunk to 10% of their original size. --- Engine/source/T3D/player.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Engine/source/T3D/player.cpp b/Engine/source/T3D/player.cpp index 3c258c374..6e33284a6 100644 --- a/Engine/source/T3D/player.cpp +++ b/Engine/source/T3D/player.cpp @@ -4659,9 +4659,9 @@ Point3F Player::_move( const F32 travelTime, Collision *outCol ) } Point3F distance = end - start; - if (mFabs(distance.x) < mObjBox.len_x() && - mFabs(distance.y) < mObjBox.len_y() && - mFabs(distance.z) < mObjBox.len_z()) + if (mFabs(distance.x) < mScaledBox.len_x() && + mFabs(distance.y) < mScaledBox.len_y() && + mFabs(distance.z) < mScaledBox.len_z()) { // We can potentially early out of this. If there are no polys in the clipped polylist at our // end position, then we can bail, and just set start = end; From f3ff1995549cda16929d761a0131214452e5187f Mon Sep 17 00:00:00 2001 From: Anis Date: Sat, 13 Feb 2016 18:50:11 +0100 Subject: [PATCH 070/109] Update navMesh.cpp --- Engine/source/navigation/navMesh.cpp | 22 ---------------------- 1 file changed, 22 deletions(-) diff --git a/Engine/source/navigation/navMesh.cpp b/Engine/source/navigation/navMesh.cpp index 74d71dc77..22a47a9f3 100644 --- a/Engine/source/navigation/navMesh.cpp +++ b/Engine/source/navigation/navMesh.cpp @@ -123,28 +123,6 @@ DefineConsoleFunction(NavMeshUpdateAll, void, (S32 objid, bool remove), (0, fals obj->enableCollision(); } -DefineConsoleFunction(NavMeshUpdateAroundObject, void, (S32 objid, bool remove), (0, false), - "@brief Update all NavMesh tiles that intersect the given object's world box.") -{ - SceneObject *obj; - if (!Sim::findObject(objid, obj)) - return; - if (remove) - obj->disableCollision(); - SimSet *set = NavMesh::getServerSet(); - for (U32 i = 0; i < set->size(); i++) - { - NavMesh *m = dynamic_cast(set->at(i)); - if (m) - { - m->cancelBuild(); - m->buildTiles(obj->getWorldBox()); - } - } - if (remove) - obj->enableCollision(); -} - DefineConsoleFunction(NavMeshUpdateOne, void, (S32 meshid, S32 objid, bool remove), (0, 0, false), "@brief Update all tiles in a given NavMesh that intersect the given object's world box.") { From 7aeec65a3ba207ff97922bea3b92b75d69e27859 Mon Sep 17 00:00:00 2001 From: Anis Date: Sat, 13 Feb 2016 20:27:30 +0100 Subject: [PATCH 071/109] Update win32Window.cpp --- .../windowManager/win32/win32Window.cpp | 173 ++++++++---------- 1 file changed, 81 insertions(+), 92 deletions(-) diff --git a/Engine/source/windowManager/win32/win32Window.cpp b/Engine/source/windowManager/win32/win32Window.cpp index 5c9b8ed11..c714586b9 100644 --- a/Engine/source/windowManager/win32/win32Window.cpp +++ b/Engine/source/windowManager/win32/win32Window.cpp @@ -26,8 +26,10 @@ #include #include #include "math/mMath.h" +#include "gfx/gfxDevice.h" #include "gfx/gfxStructs.h" +#include "windowManager/platformWindowMgr.h" #include "windowManager/win32/win32Window.h" #include "windowManager/win32/win32WindowMgr.h" #include "windowManager/win32/win32CursorController.h" @@ -39,11 +41,6 @@ // for winState structure #include "platformWin32/platformWin32.h" -#include -#include "gfx/gfxDevice.h" - -#include - const UTF16* _MainWindowClassName = L"TorqueJuggernaughtWindow"; const UTF16* _CurtainWindowClassName = L"TorqueJuggernaughtCurtainWindow"; @@ -148,96 +145,93 @@ const GFXVideoMode & Win32Window::getVideoMode() void Win32Window::setVideoMode( const GFXVideoMode &mode ) { - bool needCurtain = (mVideoMode.fullScreen != mode.fullScreen); + bool needCurtain = ( mVideoMode.fullScreen != mode.fullScreen ); - if(needCurtain) + if( needCurtain ) { - Con::errorf("Win32Window::setVideoMode - invoking curtain"); + Con::printf( "Win32Window::setVideoMode - invoking curtain" ); mOwningManager->lowerCurtain(); } - mVideoMode = mode; - mSuppressReset = true; + mVideoMode = mode; + mSuppressReset = true; // Can't switch to fullscreen while a child of another window - if(mode.fullScreen && !Platform::getWebDeployment() && mOwningManager->getParentWindow()) + if( mode.fullScreen && !Platform::getWebDeployment() && mOwningManager->getParentWindow() ) { - mOldParent = (HWND)mOwningManager->getParentWindow(); - mOwningManager->setParentWindow(NULL); + mOldParent = reinterpret_cast( mOwningManager->getParentWindow() ); + mOwningManager->setParentWindow( NULL ); } - else if(!mode.fullScreen && mOldParent) + else if( !mode.fullScreen && mOldParent ) { - mOwningManager->setParentWindow(mOldParent); + mOwningManager->setParentWindow( mOldParent ); mOldParent = NULL; } - // Set our window to have the right style based on the mode - if(mode.fullScreen && !Platform::getWebDeployment() && !mOffscreenRender) + // Set our window to have the right style based on the mode + if( mode.fullScreen && !Platform::getWebDeployment() && !mOffscreenRender ) { - WINDOWPLACEMENT wplacement = { sizeof(wplacement) }; - DWORD dwStyle = GetWindowLong(getHWND(), GWL_STYLE); - MONITORINFO mi = { sizeof(mi) }; + WINDOWPLACEMENT wplacement = { sizeof( wplacement ) }; + DWORD dwStyle = GetWindowLong( getHWND(), GWL_STYLE ); + MONITORINFO mi = { sizeof(mi) }; - if (GetWindowPlacement(getHWND(), &wplacement) && GetMonitorInfo(MonitorFromWindow(getHWND(), MONITOR_DEFAULTTOPRIMARY), &mi)) - { - DISPLAY_DEVICE dd = GetPrimaryDevice(); - DEVMODE dv; - ZeroMemory(&dv, sizeof(dv)); - dv.dmSize = sizeof(DEVMODE); - EnumDisplaySettings(dd.DeviceName, ENUM_CURRENT_SETTINGS, &dv); - dv.dmPelsWidth = mode.resolution.x; - dv.dmPelsHeight = mode.resolution.y; - dv.dmBitsPerPel = mode.bitDepth; - dv.dmDisplayFrequency = mode.refreshRate; - dv.dmFields = (DM_PELSWIDTH | DM_PELSHEIGHT); - ChangeDisplaySettings(&dv, CDS_FULLSCREEN); - SetWindowLong(getHWND(), GWL_STYLE, dwStyle & ~WS_OVERLAPPEDWINDOW); - SetWindowPos(getHWND(), HWND_TOP, - mi.rcMonitor.left, - mi.rcMonitor.top, - mi.rcMonitor.right - mi.rcMonitor.left, - mi.rcMonitor.bottom - mi.rcMonitor.top, - SWP_NOOWNERZORDER | SWP_FRAMECHANGED); - } - - if(mDisplayWindow) - ShowWindow(getHWND(), SW_SHOWNORMAL); - - // Clear the menu bar from the window for full screen - HMENU menu = GetMenu(getHWND()); - if(menu) + if ( GetWindowPlacement( getHWND(), &wplacement ) && GetMonitorInfo( MonitorFromWindow( getHWND(), MONITOR_DEFAULTTOPRIMARY ), &mi ) ) { - SetMenu(getHWND(), NULL); + DISPLAY_DEVICE dd = GetPrimaryDevice(); + DEVMODE dv; + ZeroMemory( &dv, sizeof( dv ) ); + dv.dmSize = sizeof( DEVMODE ); + EnumDisplaySettings( dd.DeviceName, ENUM_CURRENT_SETTINGS, &dv ); + dv.dmPelsWidth = mode.resolution.x; + dv.dmPelsHeight = mode.resolution.y; + dv.dmBitsPerPel = mode.bitDepth; + dv.dmDisplayFrequency = mode.refreshRate; + dv.dmFields = DM_PELSWIDTH | DM_PELSHEIGHT; + ChangeDisplaySettings( &dv, CDS_FULLSCREEN ); + SetWindowLong( getHWND(), GWL_STYLE, dwStyle & ~WS_OVERLAPPEDWINDOW ); + SetWindowPos( getHWND(), HWND_TOP, mi.rcMonitor.left, + mi.rcMonitor.top, + mi.rcMonitor.right - mi.rcMonitor.left, + mi.rcMonitor.bottom - mi.rcMonitor.top, + SWP_NOOWNERZORDER | SWP_FRAMECHANGED ); } + if( mDisplayWindow ) + ShowWindow( getHWND(), SW_SHOWNORMAL ); + + // Clear the menu bar from the window for full screen + if( GetMenu( getHWND() ) ) + SetMenu( getHWND(), NULL ); + // When switching to Fullscreen, reset device after setting style - if(mTarget.isValid()) - mTarget->resetMode(); + if( mTarget.isValid() ) + mTarget->resetMode(); mFullscreen = true; - } - else - { - DISPLAY_DEVICE dd = GetPrimaryDevice(); - DEVMODE dv; - ZeroMemory(&dv, sizeof(dv)); - dv.dmSize = sizeof(DEVMODE); - EnumDisplaySettings(dd.DeviceName, ENUM_CURRENT_SETTINGS, &dv); + } + else + { + DISPLAY_DEVICE dd = GetPrimaryDevice(); + DEVMODE dv; + ZeroMemory( &dv, sizeof( dv ) ); + dv.dmSize = sizeof( DEVMODE ); + EnumDisplaySettings( dd.DeviceName, ENUM_CURRENT_SETTINGS, &dv ); - if ((mode.resolution.x != dv.dmPelsWidth) || (mode.resolution.y != dv.dmPelsHeight)) - ChangeDisplaySettings(NULL, 0); + if ( ( WindowManager->getDesktopResolution() != mode.resolution || + ( mode.resolution.x != dv.dmPelsWidth ) || ( mode.resolution.y != dv.dmPelsHeight ) ) ) + ChangeDisplaySettings( NULL, 0 ); - // Reset device *first*, so that when we call setSize() and let it - // access the monitor settings, it won't end up with our fullscreen - // geometry that is just about to change. + // Reset device *first*, so that when we call setSize() and let it + // access the monitor settings, it won't end up with our fullscreen + // geometry that is just about to change. - if(mTarget.isValid()) - mTarget->resetMode(); + if( mTarget.isValid() ) + mTarget->resetMode(); - if (!mOffscreenRender) + if ( !mOffscreenRender ) { - SetWindowLong( getHWND(), GWL_STYLE, mWindowedWindowStyle); - SetWindowPos( getHWND(), HWND_NOTOPMOST, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE | SWP_FRAMECHANGED); + SetWindowLong( getHWND(), GWL_STYLE, mWindowedWindowStyle); + SetWindowPos( getHWND(), HWND_NOTOPMOST, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE | SWP_FRAMECHANGED); // Put back the menu bar, if any if(mMenuHandle) @@ -253,42 +247,37 @@ void Win32Window::setVideoMode( const GFXVideoMode &mode ) } else { - HWND parentWin = (HWND)mOwningManager->getParentWindow(); + HWND parentWin = reinterpret_cast( mOwningManager->getParentWindow() ); RECT windowRect; - GetClientRect(parentWin, &windowRect); - Point2I res(windowRect.right-windowRect.left, windowRect.bottom-windowRect.top); - if (res.x == 0 || res.y == 0) - { - // Must be too early in the window set up to obtain the parent's size. - setSize(mode.resolution); - } + GetClientRect( parentWin, &windowRect ); + Point2I res( windowRect.right - windowRect.left, windowRect.bottom - windowRect.top ); + + if ( res.x == 0 || res.y == 0 ) + setSize( mode.resolution ); // Must be too early in the window set up to obtain the parent's size. else - { - setSize(res); - } + setSize( res ); } - if (!mOffscreenRender) + if ( !mOffscreenRender ) { - // We have to force Win32 to update the window frame and make the window - // visible and no longer topmost - this code might be possible to simplify. - SetWindowPos( getHWND(), HWND_NOTOPMOST, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE | SWP_FRAMECHANGED); + // We have to force Win32 to update the window frame and make the window + // visible and no longer topmost - this code might be possible to simplify. + SetWindowPos( getHWND(), HWND_NOTOPMOST, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE | SWP_FRAMECHANGED ); if(mDisplayWindow) - ShowWindow( getHWND(), SW_SHOWNORMAL); + ShowWindow( getHWND(), SW_SHOWNORMAL ); } mFullscreen = false; - } + } - mSuppressReset = false; + mSuppressReset = false; - if(needCurtain) - mOwningManager->raiseCurtain(); + if( needCurtain ) + mOwningManager->raiseCurtain(); - SetForegroundWindow(getHWND()); - - getScreenResChangeSignal().trigger(this, true); + SetForegroundWindow( getHWND() ); + getScreenResChangeSignal().trigger( this, true ); } bool Win32Window::clearFullscreen() From 39613c0d87ace06e9648b3fb0d886f3c7758475c Mon Sep 17 00:00:00 2001 From: irei1as Date: Mon, 15 Feb 2016 18:43:56 +0100 Subject: [PATCH 072/109] Optimized You're right. If the normalized quaternions are in a variable or something for normal uses it's just a waste to force the change hidden inside again. --- Engine/source/math/mQuat.h | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/Engine/source/math/mQuat.h b/Engine/source/math/mQuat.h index 628e298ba..10efb0619 100644 --- a/Engine/source/math/mQuat.h +++ b/Engine/source/math/mQuat.h @@ -227,12 +227,8 @@ inline F32 QuatF::dot( const QuatF &q ) const inline F32 QuatF::angleBetween( const QuatF & q ) { - // angle between two quaternions - QuatF base(x,y,z,w); - base.normalize(); - QuatF q_norm=q; - q_norm.normalize(); - return 2.0f*mAcos(base.dot(q_norm)); + // angle between two quaternions. + return mAcos(q.dot(*this)) * 2.0f; } #endif // _MQUAT_H_ From 6891d348af015f5695d568aac6976c77b0890212 Mon Sep 17 00:00:00 2001 From: irei1as Date: Mon, 15 Feb 2016 18:50:18 +0100 Subject: [PATCH 073/109] Update mQuat.h --- Engine/source/math/mQuat.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Engine/source/math/mQuat.h b/Engine/source/math/mQuat.h index 10efb0619..ca6ed5d82 100644 --- a/Engine/source/math/mQuat.h +++ b/Engine/source/math/mQuat.h @@ -227,7 +227,7 @@ inline F32 QuatF::dot( const QuatF &q ) const inline F32 QuatF::angleBetween( const QuatF & q ) { - // angle between two quaternions. + // angle between two normalized quaternions. return mAcos(q.dot(*this)) * 2.0f; } From d25b03cd5278d75c04a204d05d55dcc81d5a7942 Mon Sep 17 00:00:00 2001 From: Azaezel Date: Mon, 15 Feb 2016 18:12:56 -0600 Subject: [PATCH 074/109] vsprintf replacement with engine vairant resolves first issue in https://github.com/GarageGames/Torque3D/issues/1515#issuecomment-184446719 --- Engine/source/core/stream/stream.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Engine/source/core/stream/stream.cpp b/Engine/source/core/stream/stream.cpp index da1b1dc82..8a7fb2b40 100644 --- a/Engine/source/core/stream/stream.cpp +++ b/Engine/source/core/stream/stream.cpp @@ -128,7 +128,7 @@ bool Stream::writeFormattedBuffer(const char *format, ...) char buffer[4096]; va_list args; va_start(args, format); - const S32 length = vsprintf(buffer, format, args); + const S32 length = dVsprintf(buffer, sizeof(buffer), format, args); // Sanity! AssertFatal(length <= sizeof(buffer), "writeFormattedBuffer - String format exceeded buffer size. This will cause corruption."); From db4755a00f75e1627552b9a0e2530a1dc7ecebbb Mon Sep 17 00:00:00 2001 From: Azaezel Date: Mon, 15 Feb 2016 19:05:09 -0600 Subject: [PATCH 075/109] namespace conflict resolution --- Engine/source/persistence/taml/fsTinyXml.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Engine/source/persistence/taml/fsTinyXml.cpp b/Engine/source/persistence/taml/fsTinyXml.cpp index 1fb97398b..a4fdafb57 100644 --- a/Engine/source/persistence/taml/fsTinyXml.cpp +++ b/Engine/source/persistence/taml/fsTinyXml.cpp @@ -38,7 +38,7 @@ bool fsTiXmlDocument::LoadFile( const char * pFilename, TiXmlEncoding encoding ) #endif // File open for read? - if ( !stream.open( filenameBuffer, Torque::FS::File::AccessMode::Read ) ) + if ( !stream.open( filenameBuffer, Torque::FS::File::Read ) ) { // No, so warn. Con::warnf("TamlXmlParser::parse() - Could not open filename '%s' for parse.", filenameBuffer ); @@ -67,7 +67,7 @@ bool fsTiXmlDocument::SaveFile( const char * pFilename ) const FileStream stream; // File opened? - if ( !stream.open( filenameBuffer, Torque::FS::File::AccessMode::Write ) ) + if ( !stream.open( filenameBuffer, Torque::FS::File::Write ) ) { // No, so warn. Con::warnf("Taml::writeFile() - Could not open filename '%s' for write.", filenameBuffer ); From f8c4dd9d1d4cf8c72eaa77e3875ee1a1073a7da3 Mon Sep 17 00:00:00 2001 From: Azaezel Date: Mon, 15 Feb 2016 19:08:03 -0600 Subject: [PATCH 076/109] http://stackoverflow.com/questions/8461832/explicit-qualification-in-declaration --- Engine/source/math/mathUtils.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Engine/source/math/mathUtils.cpp b/Engine/source/math/mathUtils.cpp index f6086c5e4..5ffd9a870 100644 --- a/Engine/source/math/mathUtils.cpp +++ b/Engine/source/math/mathUtils.cpp @@ -1847,7 +1847,7 @@ U32 extrudePolygonEdgesFromPoint( const Point3F* vertices, U32 numVertices, cons //----------------------------------------------------------------------------- -void MathUtils::mBuildHull2D(const Vector _inPoints, Vector &hullPoints) +void mBuildHull2D(const Vector _inPoints, Vector &hullPoints) { /// Andrew's monotone chain convex hull algorithm implementation From 5b5c6b9907d4b83ddc45335cac03da48a3738a36 Mon Sep 17 00:00:00 2001 From: Azaezel Date: Tue, 16 Feb 2016 01:50:58 -0600 Subject: [PATCH 077/109] Deferred Shading Phase 1: buffers engine: provides the following hooks and methods A) render target "color", and "matinfo". these correspond to texture[0] = "#color"; texture[2] = "#matinfo"; entries in scripts B) utilizes the independentMrtBitDepth method added GarageGames#857 to set color to an 8RGBA if cards support it C) adds an RenderPrePassMgr::_initShaders() method to support void RenderPrePassMgr::clearBuffers(). This operates as a pseudo-postfx by rendering a veiwspace plane which fills the screen, then calls a shader which fills both the introduced rendertarget buffers and the prepass buffer to relevant defaults (white with full alpha for prepass, black with full alpha for color and material respectively) script: \game\tools\worldEditor\main.cs adds additional hooks similar to GarageGames#863 for colorbuffer, specular map, and backbuffer display \game\core\scripts\client\lighting\advanced\deferredShading.cs adds the clearbuffer shader, visualizers, and the ShaderData( AL_DeferredShader ) + PostEffect( AL_DeferredShading ) which combine the various buffers into the output which reaches the screen under normal conditions, as well as the extended debug visualizers. again, note the lines texture[0] = "#color"; texture[1] = "#lightinfo"; texture[2] = "#matinfo"; target = "$backBuffer"; in particular for the core tie-in. shader: \game\shaders\common\lighting\advanced\deferredColorShaderP.hlsl \game\shaders\common\lighting\advanced\gl\deferredClearGBufferP.glsl the previously mentioned shaders which clear the buffers to specified colors \game\shaders\common\lighting\advanced\deferredShadingP.hlsl \game\shaders\common\lighting\advanced\gl\deferredShadingP.glsl the tie-in shaders the rest are visualizers purpose: to expose methodology that allows one to render color, lighting and material information such as specular and gloss which effect the result of both when combined long term intent: the previous prepass lighting methodology while serviceable, unfortunately had the side effect of throwing out raw color information required by more modern pipelines and methodologies. This preserves that data while also allowing the manipulation to occur only on a screenspace (or reflected speudo-screenspace) basis. --- .../renderInstance/renderPrePassMgr.cpp | 321 +++++++++++++++++- .../source/renderInstance/renderPrePassMgr.h | 20 ++ .../lighting/advanced/deferredShading.cs | 147 ++++++++ .../client/lighting/advanced/lightViz.cs | 21 +- .../lighting/advanced/dbgColorBufferP.hlsl | 31 ++ .../advanced/dbgSpecMapVisualizeP.hlsl | 32 ++ .../advanced/deferredClearGBufferP.hlsl | 47 +++ .../lighting/advanced/deferredShadingP.hlsl | 54 +++ .../lighting/advanced/gl/dbgColorBufferP.glsl | 34 ++ .../advanced/gl/dbgSpecMapVisualizeP.glsl | 34 ++ .../advanced/gl/deferredClearGBufferP.glsl | 40 +++ .../advanced/gl/deferredShadingP.glsl | 59 ++++ Templates/Full/game/tools/worldEditor/main.cs | 3 + 13 files changed, 824 insertions(+), 19 deletions(-) create mode 100644 Templates/Full/game/core/scripts/client/lighting/advanced/deferredShading.cs create mode 100644 Templates/Full/game/shaders/common/lighting/advanced/dbgColorBufferP.hlsl create mode 100644 Templates/Full/game/shaders/common/lighting/advanced/dbgSpecMapVisualizeP.hlsl create mode 100644 Templates/Full/game/shaders/common/lighting/advanced/deferredClearGBufferP.hlsl create mode 100644 Templates/Full/game/shaders/common/lighting/advanced/deferredShadingP.hlsl create mode 100644 Templates/Full/game/shaders/common/lighting/advanced/gl/dbgColorBufferP.glsl create mode 100644 Templates/Full/game/shaders/common/lighting/advanced/gl/dbgSpecMapVisualizeP.glsl create mode 100644 Templates/Full/game/shaders/common/lighting/advanced/gl/deferredClearGBufferP.glsl create mode 100644 Templates/Full/game/shaders/common/lighting/advanced/gl/deferredShadingP.glsl diff --git a/Engine/source/renderInstance/renderPrePassMgr.cpp b/Engine/source/renderInstance/renderPrePassMgr.cpp index 326f346a0..4f08dc6ea 100644 --- a/Engine/source/renderInstance/renderPrePassMgr.cpp +++ b/Engine/source/renderInstance/renderPrePassMgr.cpp @@ -36,6 +36,7 @@ #include "scene/sceneRenderState.h" #include "gfx/gfxStringEnumTranslate.h" #include "gfx/gfxDebugEvent.h" +#include "gfx/gfxCardProfile.h" #include "materials/customMaterialDefinition.h" #include "lighting/advanced/advancedLightManager.h" #include "lighting/advanced/advancedLightBinManager.h" @@ -44,10 +45,17 @@ #include "terrain/terrCellMaterial.h" #include "math/mathUtils.h" #include "math/util/matrixSet.h" +#include "gfx/gfxTextureManager.h" +#include "gfx/primBuilder.h" +#include "gfx/gfxDrawUtil.h" +#include "materials/shaderData.h" +#include "gfx/sim/cubemapData.h" const MatInstanceHookType PrePassMatInstanceHook::Type( "PrePass" ); const String RenderPrePassMgr::BufferName("prepass"); const RenderInstType RenderPrePassMgr::RIT_PrePass("PrePass"); +const String RenderPrePassMgr::ColorBufferName("color"); +const String RenderPrePassMgr::MatInfoBufferName("matinfo"); IMPLEMENT_CONOBJECT(RenderPrePassMgr); @@ -79,6 +87,7 @@ RenderPrePassMgr::RenderPrePassMgr( bool gatherDepth, mPrePassMatInstance( NULL ) { notifyType( RenderPassManager::RIT_Decal ); + notifyType( RenderPassManager::RIT_DecalRoad ); notifyType( RenderPassManager::RIT_Mesh ); notifyType( RenderPassManager::RIT_Terrain ); notifyType( RenderPassManager::RIT_Object ); @@ -90,6 +99,10 @@ RenderPrePassMgr::RenderPrePassMgr( bool gatherDepth, GFXShader::addGlobalMacro( "TORQUE_LINEAR_DEPTH" ); mNamedTarget.registerWithName( BufferName ); + mColorTarget.registerWithName( ColorBufferName ); + mMatInfoTarget.registerWithName( MatInfoBufferName ); + + mClearGBufferShader = NULL; _registerFeatures(); } @@ -98,6 +111,8 @@ RenderPrePassMgr::~RenderPrePassMgr() { GFXShader::removeGlobalMacro( "TORQUE_LINEAR_DEPTH" ); + mColorTarget.release(); + mMatInfoTarget.release(); _unregisterFeatures(); SAFE_DELETE( mPrePassMatInstance ); } @@ -119,6 +134,8 @@ bool RenderPrePassMgr::setTargetSize(const Point2I &newTargetSize) { bool ret = Parent::setTargetSize( newTargetSize ); mNamedTarget.setViewport( GFX->getViewport() ); + mColorTarget.setViewport( GFX->getViewport() ); + mMatInfoTarget.setViewport( GFX->getViewport() ); return ret; } @@ -135,6 +152,40 @@ bool RenderPrePassMgr::_updateTargets() // reload materials, the conditioner needs to alter the generated shaders } + GFXFormat colorFormat = mTargetFormat; + bool independentMrtBitDepth = GFX->getCardProfiler()->queryProfile("independentMrtBitDepth", false); + //If independent bit depth on a MRT is supported than just use 8bit channels for the albedo color. + if(independentMrtBitDepth) + colorFormat = GFXFormatR8G8B8A8; + + // andrewmac: Deferred Shading Color Buffer + if (mColorTex.getFormat() != colorFormat || mColorTex.getWidthHeight() != mTargetSize || GFX->recentlyReset()) + { + mColorTarget.release(); + mColorTex.set(mTargetSize.x, mTargetSize.y, colorFormat, + &GFXDefaultRenderTargetProfile, avar("%s() - (line %d)", __FUNCTION__, __LINE__), + 1, GFXTextureManager::AA_MATCH_BACKBUFFER); + mColorTarget.setTexture(mColorTex); + + for (U32 i = 0; i < mTargetChainLength; i++) + mTargetChain[i]->attachTexture(GFXTextureTarget::Color1, mColorTarget.getTexture()); + } + + // andrewmac: Deferred Shading Material Info Buffer + if (mMatInfoTex.getFormat() != colorFormat || mMatInfoTex.getWidthHeight() != mTargetSize || GFX->recentlyReset()) + { + mMatInfoTarget.release(); + mMatInfoTex.set(mTargetSize.x, mTargetSize.y, colorFormat, + &GFXDefaultRenderTargetProfile, avar("%s() - (line %d)", __FUNCTION__, __LINE__), + 1, GFXTextureManager::AA_MATCH_BACKBUFFER); + mMatInfoTarget.setTexture(mMatInfoTex); + + for (U32 i = 0; i < mTargetChainLength; i++) + mTargetChain[i]->attachTexture(GFXTextureTarget::Color2, mMatInfoTarget.getTexture()); + } + + GFX->finalizeReset(); + // Attach the light info buffer as a second render target, if there is // lightmapped geometry in the scene. AdvancedLightBinManager *lightBin; @@ -154,11 +205,13 @@ bool RenderPrePassMgr::_updateTargets() for ( U32 i = 0; i < mTargetChainLength; i++ ) { GFXTexHandle lightInfoTex = lightBin->getTargetTexture(0, i); - mTargetChain[i]->attachTexture(GFXTextureTarget::Color1, lightInfoTex); + mTargetChain[i]->attachTexture(GFXTextureTarget::Color3, lightInfoTex); } } } + _initShaders(); + return ret; } @@ -191,7 +244,7 @@ void RenderPrePassMgr::addElement( RenderInst *inst ) return; // First what type of render instance is it? - const bool isDecalMeshInst = inst->type == RenderPassManager::RIT_Decal; + const bool isDecalMeshInst = ((inst->type == RenderPassManager::RIT_Decal)||(inst->type == RenderPassManager::RIT_DecalRoad)); const bool isMeshInst = inst->type == RenderPassManager::RIT_Mesh; @@ -280,9 +333,8 @@ void RenderPrePassMgr::render( SceneRenderState *state ) // Tell the superclass we're about to render const bool isRenderingToTarget = _onPreRender(state); - // Clear all the buffers to white so that the - // default depth is to the far plane. - GFX->clear( GFXClearTarget | GFXClearZBuffer | GFXClearStencil, ColorI::WHITE, 1.0f, 0); + // Clear all z-buffer, and g-buffer. + clearBuffers(); // Restore transforms MatrixSet &matrixSet = getRenderPass()->getMatrixSet(); @@ -329,7 +381,13 @@ void RenderPrePassMgr::render( SceneRenderState *state ) GFX->drawPrimitive( ri->prim ); } - + // init loop data + GFXTextureObject *lastLM = NULL; + GFXCubemap *lastCubemap = NULL; + GFXTextureObject *lastReflectTex = NULL; + GFXTextureObject *lastMiscTex = NULL; + GFXTextureObject *lastAccuTex = NULL; + // Next render all the meshes. itr = mElementList.begin(); for ( ; itr != mElementList.end(); ) @@ -363,12 +421,11 @@ void RenderPrePassMgr::render( SceneRenderState *state ) // Set up SG data for this instance. setupSGData( passRI, sgData ); + mat->setSceneInfo(state, sgData); matrixSet.setWorld(*passRI->objectToWorld); matrixSet.setView(*passRI->worldToCamera); matrixSet.setProjection(*passRI->projection); - - mat->setSceneInfo(state, sgData); mat->setTransforms(matrixSet, state); // If we're instanced then don't render yet. @@ -385,6 +442,43 @@ void RenderPrePassMgr::render( SceneRenderState *state ) continue; } + bool dirty = false; + + // set the lightmaps if different + if( passRI->lightmap && passRI->lightmap != lastLM ) + { + sgData.lightmap = passRI->lightmap; + lastLM = passRI->lightmap; + dirty = true; + } + + // set the cubemap if different. + if ( passRI->cubemap != lastCubemap ) + { + sgData.cubemap = passRI->cubemap; + lastCubemap = passRI->cubemap; + dirty = true; + } + + if ( passRI->reflectTex != lastReflectTex ) + { + sgData.reflectTex = passRI->reflectTex; + lastReflectTex = passRI->reflectTex; + dirty = true; + } + + // Update accumulation texture if it changed. + // Note: accumulation texture can be NULL, and must be updated. + if (passRI->accuTex != lastAccuTex) + { + sgData.accuTex = passRI->accuTex; + lastAccuTex = lastAccuTex; + dirty = true; + } + + if ( dirty ) + mat->setTextureStages( state, sgData ); + // Setup the vertex and index buffers. mat->setBuffers( passRI->vertBuff, passRI->primBuff ); @@ -525,6 +619,31 @@ void ProcessedPrePassMaterial::_determineFeatures( U32 stageNum, #ifndef TORQUE_DEDICATED + //tag all materials running through prepass as deferred + newFeatures.addFeature(MFT_isDeferred); + + // Deferred Shading : Diffuse + if (mStages[stageNum].getTex( MFT_DiffuseMap )) + { + newFeatures.addFeature(MFT_DiffuseMap); + } + newFeatures.addFeature( MFT_DiffuseColor ); + + // Deferred Shading : Specular + if( mStages[stageNum].getTex( MFT_SpecularMap ) ) + { + newFeatures.addFeature( MFT_DeferredSpecMap ); + } + else if ( mMaterial->mPixelSpecular[stageNum] ) + { + newFeatures.addFeature( MFT_DeferredSpecVars ); + } + else + newFeatures.addFeature(MFT_DeferredEmptySpec); + + // Deferred Shading : Material Info Flags + newFeatures.addFeature( MFT_DeferredMatInfoFlags ); + for ( U32 i=0; i < fd.features.getCount(); i++ ) { const FeatureType &type = fd.features.getAt( i ); @@ -553,7 +672,10 @@ void ProcessedPrePassMaterial::_determineFeatures( U32 stageNum, type == MFT_InterlacedPrePass || type == MFT_Visibility || type == MFT_UseInstancing || - type == MFT_DiffuseVertColor ) + type == MFT_DiffuseVertColor || + type == MFT_DetailMap || + type == MFT_DetailNormalMap || + type == MFT_DiffuseMapAtlas) newFeatures.addFeature( type ); // Add any transform features. @@ -563,11 +685,39 @@ void ProcessedPrePassMaterial::_determineFeatures( U32 stageNum, newFeatures.addFeature( type ); } + if (mMaterial->mAccuEnabled[stageNum]) + { + newFeatures.addFeature(MFT_AccuMap); + mHasAccumulation = true; + } + + // we need both diffuse and normal maps + sm3 to have an accu map + if (newFeatures[MFT_AccuMap] && + (!newFeatures[MFT_DiffuseMap] || + !newFeatures[MFT_NormalMap] || + GFX->getPixelShaderVersion() < 3.0f)) { + AssertWarn(false, "SAHARA: Using an Accu Map requires SM 3.0 and a normal map."); + newFeatures.removeFeature(MFT_AccuMap); + mHasAccumulation = false; + } + + // if we still have the AccuMap feature, we add all accu constant features + if (newFeatures[MFT_AccuMap]) { + // add the dependencies of the accu map + newFeatures.addFeature(MFT_AccuScale); + newFeatures.addFeature(MFT_AccuDirection); + newFeatures.addFeature(MFT_AccuStrength); + newFeatures.addFeature(MFT_AccuCoverage); + newFeatures.addFeature(MFT_AccuSpecular); + // now remove some features that are not compatible with this + newFeatures.removeFeature(MFT_UseInstancing); + } + // If there is lightmapped geometry support, add the MRT light buffer features if(bEnableMRTLightmap) { // If this material has a lightmap, pass it through, and flag it to - // send it's output to RenderTarget1 + // send it's output to RenderTarget3 if( fd.features.hasFeature( MFT_ToneMap ) ) { newFeatures.addFeature( MFT_ToneMap ); @@ -590,10 +740,16 @@ void ProcessedPrePassMaterial::_determineFeatures( U32 stageNum, else { // If this object isn't lightmapped, add a zero-output feature to it - newFeatures.addFeature( MFT_RenderTarget1_Zero ); + newFeatures.addFeature( MFT_RenderTarget3_Zero ); } } + // cubemaps only available on stage 0 for now - bramage + if ( stageNum < 1 && + ( ( mMaterial->mCubemapData && mMaterial->mCubemapData->mCubemap ) || + mMaterial->mDynamicCubemap ) ) + newFeatures.addFeature( MFT_CubeMap ); + #endif // Set the new features. @@ -602,8 +758,54 @@ void ProcessedPrePassMaterial::_determineFeatures( U32 stageNum, U32 ProcessedPrePassMaterial::getNumStages() { - // Return 1 stage so this material gets processed for sure - return 1; + // Loops through all stages to determine how many + // stages we actually use. + // + // The first stage is always active else we shouldn't be + // creating the material to begin with. + U32 numStages = 1; + + U32 i; + for( i=1; imCubemapData || mMaterial->mDynamicCubemap ) + { + numStages++; + continue; + } + } + + // If we have a texture for the a feature the + // stage is active. + if ( mStages[i].hasValidTex() ) + stageActive = true; + + // If this stage has specular lighting, it's active + if ( mMaterial->mPixelSpecular[i] ) + stageActive = true; + + // If this stage has diffuse color, it's active + if ( mMaterial->mDiffuse[i].alpha > 0 && + mMaterial->mDiffuse[i] != ColorF::WHITE ) + stageActive = true; + + // If we have a Material that is vertex lit + // then it may not have a texture + if( mMaterial->mVertLit[i] ) + stageActive = true; + + // Increment the number of active stages + numStages += stageActive; + } + + return numStages; } void ProcessedPrePassMaterial::addStateBlockDesc(const GFXStateBlockDesc& desc) @@ -633,7 +835,7 @@ void ProcessedPrePassMaterial::addStateBlockDesc(const GFXStateBlockDesc& desc) if ( isTranslucent ) { prePassStateBlock.setBlend( true, GFXBlendSrcAlpha, GFXBlendInvSrcAlpha ); - prePassStateBlock.setColorWrites( true, true, false, false ); + prePassStateBlock.setColorWrites(false, false, false, true); } // Enable z reads, but only enable zwrites if we're not translucent. @@ -663,7 +865,22 @@ ProcessedMaterial* PrePassMatInstance::getShaderMaterial() bool PrePassMatInstance::init( const FeatureSet &features, const GFXVertexFormat *vertexFormat ) { - return Parent::init( features, vertexFormat ); + bool vaild = Parent::init(features, vertexFormat); + + if (mMaterial && mMaterial->mDiffuseMapFilename[0].isNotEmpty() && mMaterial->mDiffuseMapFilename[0].substr(0, 1).equal("#")) + { + String texTargetBufferName = mMaterial->mDiffuseMapFilename[0].substr(1, mMaterial->mDiffuseMapFilename[0].length() - 1); + NamedTexTarget *texTarget = NamedTexTarget::find(texTargetBufferName); + RenderPassData* rpd = getPass(0); + + if (rpd) + { + rpd->mTexSlot[0].texTarget = texTarget; + rpd->mTexType[0] = Material::TexTarget; + rpd->mSamplerNames[0] = "diffuseMap"; + } + } + return vaild; } PrePassMatInstanceHook::PrePassMatInstanceHook( MatInstance *baseMatInst, @@ -850,3 +1067,77 @@ Var* LinearEyeDepthConditioner::printMethodHeader( MethodType methodType, const return retVal; } + +void RenderPrePassMgr::_initShaders() +{ + if ( mClearGBufferShader ) return; + + // Find ShaderData + ShaderData *shaderData; + mClearGBufferShader = Sim::findObject( "ClearGBufferShader", shaderData ) ? shaderData->getShader() : NULL; + if ( !mClearGBufferShader ) + Con::errorf( "RenderPrePassMgr::_initShaders - could not find ClearGBufferShader" ); + + // Create StateBlocks + GFXStateBlockDesc desc; + desc.setCullMode( GFXCullNone ); + desc.setBlend( true ); + desc.setZReadWrite( false, false ); + desc.samplersDefined = true; + desc.samplers[0].addressModeU = GFXAddressWrap; + desc.samplers[0].addressModeV = GFXAddressWrap; + desc.samplers[0].addressModeW = GFXAddressWrap; + desc.samplers[0].magFilter = GFXTextureFilterLinear; + desc.samplers[0].minFilter = GFXTextureFilterLinear; + desc.samplers[0].mipFilter = GFXTextureFilterLinear; + desc.samplers[0].textureColorOp = GFXTOPModulate; + + mStateblock = GFX->createStateBlock( desc ); + + // Set up shader constants. + mShaderConsts = mClearGBufferShader->allocConstBuffer(); + mSpecularStrengthSC = mClearGBufferShader->getShaderConstHandle( "$specularStrength" ); + mSpecularPowerSC = mClearGBufferShader->getShaderConstHandle( "$specularPower" ); +} + +void RenderPrePassMgr::clearBuffers() +{ + // Clear z-buffer. + GFX->clear( GFXClearTarget | GFXClearZBuffer | GFXClearStencil, ColorI::ZERO, 1.0f, 0); + + if ( !mClearGBufferShader ) + return; + + GFXTransformSaver saver; + + // Clear the g-buffer. + RectI box(-1, -1, 3, 3); + GFX->setWorldMatrix( MatrixF::Identity ); + GFX->setViewMatrix( MatrixF::Identity ); + GFX->setProjectionMatrix( MatrixF::Identity ); + + GFX->setShader(mClearGBufferShader); + GFX->setStateBlock(mStateblock); + + Point2F nw(-0.5,-0.5); + Point2F ne(0.5,-0.5); + + GFXVertexBufferHandle verts(GFX, 4, GFXBufferTypeVolatile); + verts.lock(); + + F32 ulOffset = 0.5f - GFX->getFillConventionOffset(); + + Point2F upperLeft(-1.0, -1.0); + Point2F lowerRight(1.0, 1.0); + + verts[0].point.set( upperLeft.x+nw.x+ulOffset, upperLeft.y+nw.y+ulOffset, 0.0f ); + verts[1].point.set( lowerRight.x+ne.x, upperLeft.y+ne.y+ulOffset, 0.0f ); + verts[2].point.set( upperLeft.x-ne.x+ulOffset, lowerRight.y-ne.y, 0.0f ); + verts[3].point.set( lowerRight.x-nw.x, lowerRight.y-nw.y, 0.0f ); + + verts.unlock(); + + GFX->setVertexBuffer( verts ); + GFX->drawPrimitive( GFXTriangleStrip, 0, 2 ); + GFX->setShader(NULL); +} diff --git a/Engine/source/renderInstance/renderPrePassMgr.h b/Engine/source/renderInstance/renderPrePassMgr.h index 70a36eb84..bf36c6218 100644 --- a/Engine/source/renderInstance/renderPrePassMgr.h +++ b/Engine/source/renderInstance/renderPrePassMgr.h @@ -43,6 +43,10 @@ public: // registered buffer name static const String BufferName; + // andremwac: Deferred Rendering + static const String ColorBufferName; + static const String MatInfoBufferName; + // Generic PrePass Render Instance Type static const RenderInstType RIT_PrePass; @@ -93,6 +97,22 @@ protected: virtual void _createPrePassMaterial(); bool _lightManagerActivate(bool active); + + // Deferred Shading + GFXVertexBufferHandle mClearGBufferVerts; + GFXShaderRef mClearGBufferShader; + GFXStateBlockRef mStateblock; + NamedTexTarget mColorTarget; + NamedTexTarget mMatInfoTarget; + GFXTexHandle mColorTex; + GFXTexHandle mMatInfoTex; + GFXShaderConstBufferRef mShaderConsts; + GFXShaderConstHandle *mSpecularStrengthSC; + GFXShaderConstHandle *mSpecularPowerSC; + +public: + void clearBuffers(); + void _initShaders(); }; //------------------------------------------------------------------------------ diff --git a/Templates/Full/game/core/scripts/client/lighting/advanced/deferredShading.cs b/Templates/Full/game/core/scripts/client/lighting/advanced/deferredShading.cs new file mode 100644 index 000000000..8a1df2c67 --- /dev/null +++ b/Templates/Full/game/core/scripts/client/lighting/advanced/deferredShading.cs @@ -0,0 +1,147 @@ +singleton ShaderData( ClearGBufferShader ) +{ + DXVertexShaderFile = "shaders/common/postFx/postFxV.hlsl"; + DXPixelShaderFile = "shaders/common/lighting/advanced/deferredClearGBufferP.hlsl"; + + OGLVertexShaderFile = "shaders/common/postFx/gl/postFxV.glsl"; + OGLPixelShaderFile = "shaders/common/lighting/advanced/gl/deferredClearGBufferP.glsl"; + + pixVersion = 2.0; +}; + +singleton ShaderData( DeferredColorShader ) +{ + DXVertexShaderFile = "shaders/common/postFx/postFxV.hlsl"; + DXPixelShaderFile = "shaders/common/lighting/advanced/deferredColorShaderP.hlsl"; + + OGLVertexShaderFile = "shaders/common/postFx/gl/postFxV.glsl"; + OGLPixelShaderFile = "shaders/common/lighting/advanced/gl/deferredColorShaderP.glsl"; + + pixVersion = 2.0; +}; + +// Primary Deferred Shader +new GFXStateBlockData( AL_DeferredShadingState : PFX_DefaultStateBlock ) +{ + cullMode = GFXCullNone; + + blendDefined = true; + blendEnable = true; + blendSrc = GFXBlendSrcAlpha; + blendDest = GFXBlendInvSrcAlpha; + + samplersDefined = true; + samplerStates[0] = SamplerWrapLinear; + samplerStates[1] = SamplerWrapLinear; + samplerStates[2] = SamplerWrapLinear; + samplerStates[3] = SamplerWrapLinear; +}; + +new ShaderData( AL_DeferredShader ) +{ + DXVertexShaderFile = "shaders/common/postFx/postFxV.hlsl"; + DXPixelShaderFile = "shaders/common/lighting/advanced/deferredShadingP.hlsl"; + + OGLVertexShaderFile = "shaders/common/postFx/gl/postFxV.glsl"; + OGLPixelShaderFile = "shaders/common/lighting/advanced/gl/deferredShadingP.glsl"; + + samplerNames[0] = "colorBufferTex"; + samplerNames[1] = "lightPrePassTex"; + samplerNames[2] = "matInfoTex"; + samplerNames[3] = "prepassTex"; + + pixVersion = 2.0; +}; + +singleton PostEffect( AL_DeferredShading ) +{ + renderTime = "PFXBeforeBin"; + renderBin = "SkyBin"; + shader = AL_DeferredShader; + stateBlock = AL_DeferredShadingState; + texture[0] = "#color"; + texture[1] = "#lightinfo"; + texture[2] = "#matinfo"; + texture[3] = "#prepass"; + + target = "$backBuffer"; + renderPriority = 10000; + allowReflectPass = true; +}; + +// Debug Shaders. +new ShaderData( AL_ColorBufferShader ) +{ + DXVertexShaderFile = "shaders/common/postFx/postFxV.hlsl"; + DXPixelShaderFile = "shaders/common/lighting/advanced/dbgColorBufferP.hlsl"; + + OGLVertexShaderFile = "shaders/common/postFx/gl/postFxV.glsl"; + OGLPixelShaderFile = "shaders/common/lighting/advanced/gl/dbgColorBufferP.glsl"; + + samplerNames[0] = "colorBufferTex"; + pixVersion = 2.0; +}; + +singleton PostEffect( AL_ColorBufferVisualize ) +{ + shader = AL_ColorBufferShader; + stateBlock = AL_DefaultVisualizeState; + texture[0] = "#color"; + target = "$backBuffer"; + renderPriority = 9999; +}; + +/// Toggles the visualization of the AL lighting specular power buffer. +function toggleColorBufferViz( %enable ) +{ + if ( %enable $= "" ) + { + $AL_ColorBufferShaderVar = AL_ColorBufferVisualize.isEnabled() ? false : true; + AL_ColorBufferVisualize.toggle(); + } + else if ( %enable ) + { + AL_DeferredShading.disable(); + AL_ColorBufferVisualize.enable(); + } + else if ( !%enable ) + { + AL_ColorBufferVisualize.disable(); + AL_DeferredShading.enable(); + } +} + +new ShaderData( AL_SpecMapShader ) +{ + DXVertexShaderFile = "shaders/common/postFx/postFxV.hlsl"; + DXPixelShaderFile = "shaders/common/lighting/advanced/dbgSpecMapVisualizeP.hlsl"; + + OGLVertexShaderFile = "shaders/common/postFx/gl/postFxV.glsl"; + OGLPixelShaderFile = "shaders/common/lighting/advanced/gl/dbgSpecMapVisualizeP.glsl"; + + samplerNames[0] = "matinfoTex"; + pixVersion = 2.0; +}; + +singleton PostEffect( AL_SpecMapVisualize ) +{ + shader = AL_SpecMapShader; + stateBlock = AL_DefaultVisualizeState; + texture[0] = "#matinfo"; + target = "$backBuffer"; + renderPriority = 9999; +}; + +/// Toggles the visualization of the AL lighting specular power buffer. +function toggleSpecMapViz( %enable ) +{ + if ( %enable $= "" ) + { + $AL_SpecMapShaderVar = AL_SpecMapVisualize.isEnabled() ? false : true; + AL_SpecMapVisualize.toggle(); + } + else if ( %enable ) + AL_SpecMapVisualize.enable(); + else if ( !%enable ) + AL_SpecMapVisualize.disable(); +} \ No newline at end of file diff --git a/Templates/Full/game/core/scripts/client/lighting/advanced/lightViz.cs b/Templates/Full/game/core/scripts/client/lighting/advanced/lightViz.cs index 22665120d..fcaf72942 100644 --- a/Templates/Full/game/core/scripts/client/lighting/advanced/lightViz.cs +++ b/Templates/Full/game/core/scripts/client/lighting/advanced/lightViz.cs @@ -56,7 +56,7 @@ new ShaderData( AL_DepthVisualizeShader ) OGLVertexShaderFile = "shaders/common/postFx/gl/postFxV.glsl"; OGLPixelShaderFile = "shaders/common/lighting/advanced/gl/dbgDepthVisualizeP.glsl"; - samplerNames[0] = "prepassBuffer"; + samplerNames[0] = "prepassTex"; samplerNames[1] = "depthViz"; pixVersion = 2.0; @@ -113,7 +113,7 @@ new ShaderData( AL_NormalsVisualizeShader ) OGLVertexShaderFile = "shaders/common/postFx/gl/postFxV.glsl"; OGLPixelShaderFile = "shaders/common/lighting/advanced/gl/dbgNormalVisualizeP.glsl"; - samplerNames[0] = "prepassBuffer"; + samplerNames[0] = "prepassTex"; pixVersion = 2.0; }; @@ -149,7 +149,7 @@ new ShaderData( AL_LightColorVisualizeShader ) OGLVertexShaderFile = "shaders/common/postFx/gl/postFxV.glsl"; OGLPixelShaderFile = "shaders/common/lighting/advanced/gl/dbgLightColorVisualizeP.glsl"; - samplerNames[0] = "lightInfoBuffer"; + samplerNames[0] = "lightPrePassTex"; pixVersion = 2.0; }; @@ -184,7 +184,7 @@ new ShaderData( AL_LightSpecularVisualizeShader ) OGLVertexShaderFile = "shaders/common/postFx/gl/postFxV.glsl"; OGLPixelShaderFile = "shaders/common/lighting/advanced/gl/dbgLightSpecularVisualizeP.glsl"; - samplerNames[0] = "lightInfoBuffer"; + samplerNames[0] = "lightPrePassTex"; pixVersion = 2.0; }; @@ -280,3 +280,16 @@ function toggleLightSpecularViz( %enable ) AL_LightSpecularVisualize.disable(); } +function toggleBackbufferViz( %enable ) +{ + if ( %enable $= "" ) + { + $AL_BackbufferVisualizeVar = AL_DeferredShading.isEnabled() ? true : false; + AL_DeferredShading.toggle(); + } + else if ( %enable ) + AL_DeferredShading.disable(); + else if ( !%enable ) + AL_DeferredShading.enable(); +} + diff --git a/Templates/Full/game/shaders/common/lighting/advanced/dbgColorBufferP.hlsl b/Templates/Full/game/shaders/common/lighting/advanced/dbgColorBufferP.hlsl new file mode 100644 index 000000000..349c943f9 --- /dev/null +++ b/Templates/Full/game/shaders/common/lighting/advanced/dbgColorBufferP.hlsl @@ -0,0 +1,31 @@ +//----------------------------------------------------------------------------- +// Copyright (c) 2012 GarageGames, LLC +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. +//----------------------------------------------------------------------------- + +#include "shadergen:/autogenConditioners.h" +#include "../../postfx/postFx.hlsl" + + +float4 main( PFXVertToPix IN, + uniform sampler2D colorBufferTex : register(S0) ) : COLOR0 +{ + return float4(tex2D( colorBufferTex, IN.uv0 ).rgb, 1.0); +} diff --git a/Templates/Full/game/shaders/common/lighting/advanced/dbgSpecMapVisualizeP.hlsl b/Templates/Full/game/shaders/common/lighting/advanced/dbgSpecMapVisualizeP.hlsl new file mode 100644 index 000000000..ba5f2c0e1 --- /dev/null +++ b/Templates/Full/game/shaders/common/lighting/advanced/dbgSpecMapVisualizeP.hlsl @@ -0,0 +1,32 @@ +//----------------------------------------------------------------------------- +// Copyright (c) 2012 GarageGames, LLC +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. +//----------------------------------------------------------------------------- + +#include "shadergen:/autogenConditioners.h" +#include "../../postfx/postFx.hlsl" + + +float4 main( PFXVertToPix IN, + uniform sampler2D matinfoTex : register(S0) ) : COLOR0 +{ + float specular = tex2D( matinfoTex, IN.uv0 ).b; + return float4( specular, specular, specular, 1.0 ); +} diff --git a/Templates/Full/game/shaders/common/lighting/advanced/deferredClearGBufferP.hlsl b/Templates/Full/game/shaders/common/lighting/advanced/deferredClearGBufferP.hlsl new file mode 100644 index 000000000..df9870248 --- /dev/null +++ b/Templates/Full/game/shaders/common/lighting/advanced/deferredClearGBufferP.hlsl @@ -0,0 +1,47 @@ +//----------------------------------------------------------------------------- +// Copyright (c) 2012 GarageGames, LLC +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. +//----------------------------------------------------------------------------- + +struct Fragout +{ + float4 col : COLOR0; + float4 col1 : COLOR1; + float4 col2 : COLOR2; +}; + +//----------------------------------------------------------------------------- +// Main +//----------------------------------------------------------------------------- +Fragout main( ) +{ + Fragout OUT; + + // Clear Prepass Buffer ( Normals/Depth ); + OUT.col = float4(1.0, 1.0, 1.0, 1.0); + + // Clear Color Buffer. + OUT.col1 = float4(0.0, 0.0, 0.0, 1.0); + + // Clear Material Info Buffer. + OUT.col2 = float4(0.0, 0.0, 0.0, 1.0); + + return OUT; +} diff --git a/Templates/Full/game/shaders/common/lighting/advanced/deferredShadingP.hlsl b/Templates/Full/game/shaders/common/lighting/advanced/deferredShadingP.hlsl new file mode 100644 index 000000000..80e6acde0 --- /dev/null +++ b/Templates/Full/game/shaders/common/lighting/advanced/deferredShadingP.hlsl @@ -0,0 +1,54 @@ +//----------------------------------------------------------------------------- +// Copyright (c) 2012 GarageGames, LLC +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. +//----------------------------------------------------------------------------- + +#include "shadergen:/autogenConditioners.h" +#include "../../postfx/postFx.hlsl" +#include "shaders/common/torque.hlsl" + + +float4 main( PFXVertToPix IN, + uniform sampler2D colorBufferTex : register(S0), + uniform sampler2D lightPrePassTex : register(S1), + uniform sampler2D matInfoTex : register(S2), + uniform sampler2D prepassTex : register(S3)) : COLOR0 +{ + float4 lightBuffer = tex2D( lightPrePassTex, IN.uv0 ); + float4 colorBuffer = tex2D( colorBufferTex, IN.uv0 ); + float4 matInfo = tex2D( matInfoTex, IN.uv0 ); + float specular = saturate(lightBuffer.a); + float depth = prepassUncondition( prepassTex, IN.uv0 ).w; + + if (depth>0.9999) + return float4(0,0,0,0); + + // Diffuse Color Altered by Metalness + bool metalness = getFlag(matInfo.r, 3); + if ( metalness ) + { + colorBuffer *= (1.0 - colorBuffer.a); + } + + colorBuffer *= float4(lightBuffer.rgb, 1.0); + colorBuffer += float4(specular, specular, specular, 1.0); + + return hdrEncode( float4(colorBuffer.rgb, 1.0) ); +} diff --git a/Templates/Full/game/shaders/common/lighting/advanced/gl/dbgColorBufferP.glsl b/Templates/Full/game/shaders/common/lighting/advanced/gl/dbgColorBufferP.glsl new file mode 100644 index 000000000..48a96d47d --- /dev/null +++ b/Templates/Full/game/shaders/common/lighting/advanced/gl/dbgColorBufferP.glsl @@ -0,0 +1,34 @@ +//----------------------------------------------------------------------------- +// Copyright (c) 2012 GarageGames, LLC +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. +//----------------------------------------------------------------------------- + +#include "../../../gl/hlslCompat.glsl" +#include "shadergen:/autogenConditioners.h" +#include "../../../postfx/gl/postFx.glsl" + +uniform sampler2D colorBufferTex; + +out vec4 OUT_FragColor0; + +void main() +{ + OUT_FragColor0 = vec4(texture( colorBufferTex, uv0 ).rgb, 1.0); +} diff --git a/Templates/Full/game/shaders/common/lighting/advanced/gl/dbgSpecMapVisualizeP.glsl b/Templates/Full/game/shaders/common/lighting/advanced/gl/dbgSpecMapVisualizeP.glsl new file mode 100644 index 000000000..4ba9f6734 --- /dev/null +++ b/Templates/Full/game/shaders/common/lighting/advanced/gl/dbgSpecMapVisualizeP.glsl @@ -0,0 +1,34 @@ +//----------------------------------------------------------------------------- +// Copyright (c) 2012 GarageGames, LLC +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. +//----------------------------------------------------------------------------- +#include "../../../gl/hlslCompat.glsl" +#include "shadergen:/autogenConditioners.h" +#include "../../../postfx/gl/postFx.glsl" + +uniform sampler2D matinfoTex; + +out vec4 OUT_FragColor0; + +void main() +{ + float specular = texture( matinfoTex, uv0 ).a; + OUT_FragColor0 = vec4( specular, specular, specular, 1.0 ); +} diff --git a/Templates/Full/game/shaders/common/lighting/advanced/gl/deferredClearGBufferP.glsl b/Templates/Full/game/shaders/common/lighting/advanced/gl/deferredClearGBufferP.glsl new file mode 100644 index 000000000..39dc0dc9f --- /dev/null +++ b/Templates/Full/game/shaders/common/lighting/advanced/gl/deferredClearGBufferP.glsl @@ -0,0 +1,40 @@ +//----------------------------------------------------------------------------- +// Copyright (c) 2012 GarageGames, LLC +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. +//----------------------------------------------------------------------------- + +out vec4 OUT_col; +out vec4 OUT_col1; +out vec4 OUT_col2; + +//----------------------------------------------------------------------------- +// Main +//----------------------------------------------------------------------------- +void main() +{ + // Clear Prepass Buffer ( Normals/Depth ); + OUT_col = vec4(1.0, 1.0, 1.0, 1.0); + + // Clear Color Buffer. + OUT_col1 = vec4(0.0, 0.0, 0.0, 1.0); + + // Clear Material Info Buffer. + OUT_col2 = vec4(0.0, 0.0, 0.0, 1.0); +} diff --git a/Templates/Full/game/shaders/common/lighting/advanced/gl/deferredShadingP.glsl b/Templates/Full/game/shaders/common/lighting/advanced/gl/deferredShadingP.glsl new file mode 100644 index 000000000..4ee4b1d81 --- /dev/null +++ b/Templates/Full/game/shaders/common/lighting/advanced/gl/deferredShadingP.glsl @@ -0,0 +1,59 @@ +//----------------------------------------------------------------------------- +// Copyright (c) 2012 GarageGames, LLC +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. +//----------------------------------------------------------------------------- + +#include "../../../gl/hlslCompat.glsl" +#include "shadergen:/autogenConditioners.h" +#include "../../../postfx/gl/postFx.glsl" +#include "../../../gl/torque.glsl" + +uniform sampler2D colorBufferTex; +uniform sampler2D lightPrePassTex; +uniform sampler2D matInfoTex; +uniform sampler2D prepassTex; + +out vec4 OUT_col; + +void main() +{ + float depth = prepassUncondition( prepassTex, uv0 ).w; + if (depth>0.9999) + { + OUT_col = vec4(0.0); + return; + } + vec4 lightBuffer = texture( lightPrePassTex, uv0 ); + vec4 colorBuffer = texture( colorBufferTex, uv0 ); + vec4 matInfo = texture( matInfoTex, uv0 ); + float specular = clamp(lightBuffer.a,0.0,1.0); + + // Diffuse Color Altered by Metalness + bool metalness = getFlag(matInfo.r, 3); + if ( metalness ) + { + colorBuffer *= (1.0 - colorBuffer.a); + } + + colorBuffer *= vec4(lightBuffer.rgb, 1.0); + colorBuffer += vec4(specular, specular, specular, 1.0); + + OUT_col = hdrEncode( vec4(colorBuffer.rgb, 1.0) ); +} diff --git a/Templates/Full/game/tools/worldEditor/main.cs b/Templates/Full/game/tools/worldEditor/main.cs index a6ecb2872..59301ea53 100644 --- a/Templates/Full/game/tools/worldEditor/main.cs +++ b/Templates/Full/game/tools/worldEditor/main.cs @@ -120,6 +120,9 @@ function initializeWorldEditor() EVisibility.addOption( "AL: Light Specular Viz", "$AL_LightSpecularVisualizeVar", "toggleLightSpecularViz" ); EVisibility.addOption( "AL: Normals Viz", "$AL_NormalsVisualizeVar", "toggleNormalsViz" ); EVisibility.addOption( "AL: Depth Viz", "$AL_DepthVisualizeVar", "toggleDepthViz" ); + EVisibility.addOption( "AL: Color Buffer", "$AL_ColorBufferShaderVar", "toggleColorBufferViz" ); + EVisibility.addOption( "AL: Spec Map", "$AL_SpecMapShaderVar", "toggleSpecMapViz"); + EVisibility.addOption( "AL: Backbuffer", "$AL_BackbufferVisualizeVar", "toggleBackbufferViz" ); EVisibility.addOption( "AL: Glow Buffer", "$AL_GlowVisualizeVar", "toggleGlowViz" ); EVisibility.addOption( "AL: PSSM Cascade Viz", "$AL::PSSMDebugRender", "" ); EVisibility.addOption( "Frustum Lock", "$Scene::lockCull", "" ); From 196b214eae6d27abcdbdb94fe689938b69821e18 Mon Sep 17 00:00:00 2001 From: Azaezel Date: Tue, 16 Feb 2016 02:23:23 -0600 Subject: [PATCH 078/109] engine: defines and alters a series of material features for deferred shading in order to define a fully fleshed out multiple render target gbuffer patterned after the general principles outlined http://www.catalinzima.com/xna/tutorials/deferred-rendering-in-xna/creating-the-g-buffer/ (though I cannot stress enough *not* using the identical layout) script: removes dead material features (ie: those that never functioned to begin with) shader: bool getFlag(float flags, int num) definition for retreiving data from the 3rd (matinfo) gbuffer slot's red channel (more on that shortly) purpose: _A)_ Small primer on how material features function: When a https://github.com/GarageGames/Torque3D/search?utf8=%E2%9C%93&q=_determineFeatures call is executed, certain conditions trigger a given .addFeature(MFT_SOMEFEATURE) call based upon material definition entries, be it a value, a texture reference, or even the presence or lack thereof for another feature. In general terms, the first to be executed is ProcessedShaderMaterial::_determineFeatures followed by ProcessedPrePassMaterial::_determineFeatures. The next commit will provide the bindings there. For now it's enough to understand that one of those two will trigger the shadergen subsystem, when rendering a material, to check it's associated list of features and spit out a shader if one is not already defined, or reference a pre-existing one that includes codelines determined by that list of features. Relevant execution of this is as follows: DeclareFeatureType( MFT_DeferredDiffuseMap ); - Name ImplementFeatureType( MFT_DeferredDiffuseMap, MFG_Texture, 2.0f, false ); - Codeline Insertion Order FEATUREMGR->registerFeature( MFT_DeferredDiffuseMap, new DeferredDiffuseMapHLSL ); - Hook to class which actually generates code alternately FEATUREMGR->registerFeature( MFT_Imposter, new NamedFeatureHLSL( "Imposter" ) ); - a simple feature that serves no purpose further than as a test of it's existence (to modify other features for instance) class DeferredDiffuseMapHLSL : public ShaderFeatureHLSL - Class definition { getName -embeded in the proceedural shader as a remline both up top and before actual code insertions processPix - pixel shader codeline insertions getOutputTargets - used to determine which buffer is written to (assumes only one. depending on branched logic, older features that may be run for either forward or deferred rendering depending on circumstance may have a logical switch based on additional feature flags. as an example: TerrainBaseMapFeatHLSL::getOutputTargets) getResources - associated with the Resources struct, closely aligned with the hardware regestry getBlendOp - used to determine what blend operation to use if a material requires a second pass (defaults to overwriting) setTexData - ??? processVert - vertex shader codeline insertions }; _B)_ The resultant Gbuffer layout defined by the previous commit therefore is as follows: defaultrendertarget (referred to in shaders as out.col or col depending on GFX plugin) contains either lighting and normal data, or color data depending on if it is used in a deferred or forward lit manner (note for forward lit, this data is replaced as a second step with color. why custommaterials have traditionally had problems with lighting) color1 (referred to in shaders as out.col1 or col1 depending on GFX plugin) RGB color data and an A for blending operations (including transparency) color2 (referred to in shaders as out.col2 or col2 depending on GFX plugin) contains: red channel comprising material flags such as metalness, emissive, ect, green channel for translucency (light shining through, as oposed to see-through transparency), blue for blue for specular strength (how much light influences net color) alpha for specular power (generally how reflective/glossy an object is) long term purpose: further down the line, these will be used to condition data for use with a PBR subsystem, with further corrections to the underlying mathematics, strength being replaced by roughness, and power by metalness --- .../glsl/deferredShadingFeaturesGLSL.cpp | 190 +++++++++++++ .../glsl/deferredShadingFeaturesGLSL.h | 85 ++++++ .../hlsl/deferredShadingFeaturesHLSL.cpp | 187 +++++++++++++ .../hlsl/deferredShadingFeaturesHLSL.h | 84 ++++++ .../source/materials/materialFeatureTypes.cpp | 20 +- .../source/materials/materialFeatureTypes.h | 12 +- .../shaderGen/GLSL/shaderFeatureGLSL.cpp | 184 +++++++++---- .../source/shaderGen/GLSL/shaderFeatureGLSL.h | 11 + Engine/source/shaderGen/GLSL/shaderGenGLSL.h | 5 +- .../shaderGen/GLSL/shaderGenGLSLInit.cpp | 21 +- .../shaderGen/HLSL/shaderFeatureHLSL.cpp | 152 ++++++++-- .../source/shaderGen/HLSL/shaderFeatureHLSL.h | 11 + .../shaderGen/HLSL/shaderGenHLSLInit.cpp | 12 + Engine/source/shaderGen/shaderFeature.cpp | 18 +- Engine/source/shaderGen/shaderGenVars.cpp | 5 +- Engine/source/shaderGen/shaderGenVars.h | 3 + .../source/terrain/glsl/terrFeatureGLSL.cpp | 259 ++++++++++++------ Engine/source/terrain/glsl/terrFeatureGLSL.h | 17 ++ .../source/terrain/hlsl/terrFeatureHLSL.cpp | 179 ++++++------ Engine/source/terrain/hlsl/terrFeatureHLSL.h | 17 ++ Engine/source/terrain/terrCellMaterial.cpp | 30 +- Engine/source/terrain/terrFeatureTypes.cpp | 6 +- Engine/source/terrain/terrFeatureTypes.h | 6 + .../game/shaders/common/terrain/terrain.glsl | 1 + .../gui/guiMaterialPropertiesWindow.ed.gui | 228 +-------------- 25 files changed, 1257 insertions(+), 486 deletions(-) create mode 100644 Engine/source/lighting/advanced/glsl/deferredShadingFeaturesGLSL.cpp create mode 100644 Engine/source/lighting/advanced/glsl/deferredShadingFeaturesGLSL.h create mode 100644 Engine/source/lighting/advanced/hlsl/deferredShadingFeaturesHLSL.cpp create mode 100644 Engine/source/lighting/advanced/hlsl/deferredShadingFeaturesHLSL.h diff --git a/Engine/source/lighting/advanced/glsl/deferredShadingFeaturesGLSL.cpp b/Engine/source/lighting/advanced/glsl/deferredShadingFeaturesGLSL.cpp new file mode 100644 index 000000000..e457b9eab --- /dev/null +++ b/Engine/source/lighting/advanced/glsl/deferredShadingFeaturesGLSL.cpp @@ -0,0 +1,190 @@ +//----------------------------------------------------------------------------- +// Copyright (c) 2012 GarageGames, LLC +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. +//----------------------------------------------------------------------------- + +#include "platform/platform.h" +#include "lighting/advanced/glsl/deferredShadingFeaturesGLSL.h" + +#include "lighting/advanced/advancedLightBinManager.h" +#include "shaderGen/langElement.h" +#include "shaderGen/shaderOp.h" +#include "shaderGen/conditionerFeature.h" +#include "renderInstance/renderPrePassMgr.h" +#include "materials/processedMaterial.h" +#include "materials/materialFeatureTypes.h" + + +//**************************************************************************** +// Deferred Shading Features +//**************************************************************************** + +// Specular Map -> Blue of Material Buffer ( greyscaled ) +// Gloss Map (Alpha Channel of Specular Map) -> Alpha ( Spec Power ) of Material Info Buffer. +void DeferredSpecMapGLSL::processPix( Vector &componentList, const MaterialFeatureData &fd ) +{ + // Get the texture coord. + Var *texCoord = getInTexCoord( "texCoord", "vec2", true, componentList ); + + // search for color var + Var *material = (Var*) LangElement::find( getOutputTargetVarName(ShaderFeature::RenderTarget2) ); + MultiLine * meta = new MultiLine; + if ( !material ) + { + // create color var + material = new Var; + material->setType( "vec4" ); + material->setName( getOutputTargetVarName(ShaderFeature::RenderTarget2) ); + material->setStructName("OUT"); + } + + // create texture var + Var *specularMap = new Var; + specularMap->setType( "sampler2D" ); + specularMap->setName( "specularMap" ); + specularMap->uniform = true; + specularMap->sampler = true; + specularMap->constNum = Var::getTexUnitNum(); + LangElement *texOp = new GenOp( "tex2D(@, @)", specularMap, texCoord ); + //matinfo.g slot reserved for AO later + meta->addStatement(new GenOp(" @.g = 1.0;\r\n", material)); + meta->addStatement(new GenOp(" @.b = dot(tex2D(@, @).rgb, vec3(0.3, 0.59, 0.11));\r\n", material, specularMap, texCoord)); + meta->addStatement(new GenOp(" @.a = tex2D(@, @).a;\r\n", material, specularMap, texCoord)); + output = meta; +} + +ShaderFeature::Resources DeferredSpecMapGLSL::getResources( const MaterialFeatureData &fd ) +{ + Resources res; + res.numTex = 1; + res.numTexReg = 1; + + return res; +} + +void DeferredSpecMapGLSL::setTexData( Material::StageData &stageDat, + const MaterialFeatureData &fd, + RenderPassData &passData, + U32 &texIndex ) +{ + GFXTextureObject *tex = stageDat.getTex( MFT_SpecularMap ); + if ( tex ) + { + passData.mTexType[ texIndex ] = Material::Standard; + passData.mSamplerNames[ texIndex ] = "specularMap"; + passData.mTexSlot[ texIndex++ ].texObject = tex; + } +} + +void DeferredSpecMapGLSL::processVert( Vector &componentList, + const MaterialFeatureData &fd ) +{ + MultiLine *meta = new MultiLine; + getOutTexCoord( "texCoord", + "vec2", + true, + fd.features[MFT_TexAnim], + meta, + componentList ); + output = meta; +} + +// Material Info Flags -> Red ( Flags ) of Material Info Buffer. +void DeferredMatInfoFlagsGLSL::processPix( Vector &componentList, const MaterialFeatureData &fd ) +{ + MultiLine *meta = new MultiLine; + + // search for material var + Var *material = (Var*) LangElement::find( getOutputTargetVarName(ShaderFeature::RenderTarget2) ); + if ( !material ) + { + // create material var + material = new Var; + material->setType( "vec4" ); + material->setName( getOutputTargetVarName(ShaderFeature::RenderTarget2) ); + material->setStructName("OUT"); + } + + Var *matInfoFlags = new Var; + matInfoFlags->setType( "float" ); + matInfoFlags->setName( "matInfoFlags" ); + matInfoFlags->uniform = true; + matInfoFlags->constSortPos = cspPotentialPrimitive; + + meta->addStatement(output = new GenOp(" @.r = @;\r\n", material, matInfoFlags)); + output = meta; +} + +// Spec Strength -> Blue Channel of Material Info Buffer. +// Spec Power -> Alpha Channel ( of Material Info Buffer. +void DeferredSpecVarsGLSL::processPix( Vector &componentList, const MaterialFeatureData &fd ) +{ + + // search for material var + Var *material = (Var*) LangElement::find( getOutputTargetVarName(ShaderFeature::RenderTarget2) ); + if ( !material ) + { + // create material var + material = new Var; + material->setType( "vec4" ); + material->setName( getOutputTargetVarName(ShaderFeature::RenderTarget2) ); + material->setStructName("OUT"); + } + + Var *specStrength = new Var; + specStrength->setType( "float" ); + specStrength->setName( "specularStrength" ); + specStrength->uniform = true; + specStrength->constSortPos = cspPotentialPrimitive; + + Var *specPower = new Var; + specPower->setType("float"); + specPower->setName("specularPower"); + specPower->uniform = true; + specPower->constSortPos = cspPotentialPrimitive; + + MultiLine *meta = new MultiLine; + //matinfo.g slot reserved for AO later + meta->addStatement(new GenOp(" @.g = 1.0;\r\n", material)); + meta->addStatement(new GenOp(" @.b = @/128;\r\n", material, specStrength)); + meta->addStatement(new GenOp(" @.a = @/5;\r\n", material, specPower)); + output = meta; +} + +// Black -> Blue and Alpha of Color Buffer (representing no specular) +void DeferredEmptySpecGLSL::processPix( Vector &componentList, const MaterialFeatureData &fd ) +{ + // search for material var + Var *material = (Var*) LangElement::find( getOutputTargetVarName(ShaderFeature::RenderTarget2) ); + if ( !material ) + { + // create material var + material = new Var; + material->setType( "vec4" ); + material->setName( getOutputTargetVarName(ShaderFeature::RenderTarget2) ); + material->setStructName("OUT"); + } + + MultiLine * meta = new MultiLine; + //matinfo.g slot reserved for AO later + meta->addStatement(new GenOp(" @.g = 1.0;\r\n", material)); + meta->addStatement(new GenOp(" @.ba = vec2(0.0);\r\n", material)); + output = meta; +} diff --git a/Engine/source/lighting/advanced/glsl/deferredShadingFeaturesGLSL.h b/Engine/source/lighting/advanced/glsl/deferredShadingFeaturesGLSL.h new file mode 100644 index 000000000..f687bd917 --- /dev/null +++ b/Engine/source/lighting/advanced/glsl/deferredShadingFeaturesGLSL.h @@ -0,0 +1,85 @@ +//----------------------------------------------------------------------------- +// Copyright (c) 2012 GarageGames, LLC +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. +//----------------------------------------------------------------------------- + +#ifndef _DEFERREDSHADINGGLSL_H_ +#define _DEFERREDSHADINGGLSL_H_ + +#include "shaderGen/GLSL/shaderFeatureGLSL.h" +#include "shaderGen/GLSL/bumpGLSL.h" +#include "shaderGen/GLSL/pixSpecularGLSL.h" + +// Specular Outputs +class DeferredSpecMapGLSL : public ShaderFeatureGLSL +{ +public: + virtual String getName() { return "Deferred Shading: Specular Map"; } + + virtual void processPix( Vector &componentList, + const MaterialFeatureData &fd ); + + virtual Resources getResources( const MaterialFeatureData &fd ); + + // Sets textures and texture flags for current pass + virtual void setTexData( Material::StageData &stageDat, + const MaterialFeatureData &fd, + RenderPassData &passData, + U32 &texIndex ); + + virtual void processVert( Vector &componentList, + const MaterialFeatureData &fd ); +}; + +class DeferredMatInfoFlagsGLSL : public ShaderFeatureGLSL +{ +public: + virtual String getName() { return "Deferred Shading: Mat Info Flags"; } + + virtual void processPix( Vector &componentList, + const MaterialFeatureData &fd ); + + virtual U32 getOutputTargets( const MaterialFeatureData &fd ) const { return ShaderFeature::RenderTarget2; } +}; + +class DeferredSpecVarsGLSL : public ShaderFeatureGLSL +{ +public: + virtual String getName() { return "Deferred Shading: Specular Explicit Numbers"; } + + virtual void processPix( Vector &componentList, + const MaterialFeatureData &fd ); + + virtual U32 getOutputTargets( const MaterialFeatureData &fd ) const { return ShaderFeature::RenderTarget2; } +}; + + +class DeferredEmptySpecGLSL : public ShaderFeatureGLSL +{ +public: + virtual String getName() { return "Deferred Shading: Empty Specular"; } + + virtual void processPix( Vector &componentList, + const MaterialFeatureData &fd ); + + virtual U32 getOutputTargets( const MaterialFeatureData &fd ) const { return ShaderFeature::RenderTarget2; } +}; + +#endif \ No newline at end of file diff --git a/Engine/source/lighting/advanced/hlsl/deferredShadingFeaturesHLSL.cpp b/Engine/source/lighting/advanced/hlsl/deferredShadingFeaturesHLSL.cpp new file mode 100644 index 000000000..6b8b990a0 --- /dev/null +++ b/Engine/source/lighting/advanced/hlsl/deferredShadingFeaturesHLSL.cpp @@ -0,0 +1,187 @@ +//----------------------------------------------------------------------------- +// Copyright (c) 2012 GarageGames, LLC +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. +//----------------------------------------------------------------------------- + +#include "platform/platform.h" +#include "lighting/advanced/hlsl/deferredShadingFeaturesHLSL.h" + +#include "lighting/advanced/advancedLightBinManager.h" +#include "shaderGen/langElement.h" +#include "shaderGen/shaderOp.h" +#include "shaderGen/conditionerFeature.h" +#include "renderInstance/renderPrePassMgr.h" +#include "materials/processedMaterial.h" +#include "materials/materialFeatureTypes.h" + + +//**************************************************************************** +// Deferred Shading Features +//**************************************************************************** + +// Specular Map -> Blue of Material Buffer ( greyscaled ) +// Gloss Map (Alpha Channel of Specular Map) -> Alpha ( Spec Power ) of Material Info Buffer. +void DeferredSpecMapHLSL::processPix( Vector &componentList, const MaterialFeatureData &fd ) +{ + // Get the texture coord. + Var *texCoord = getInTexCoord( "texCoord", "float2", true, componentList ); + + // search for color var + Var *material = (Var*) LangElement::find( getOutputTargetVarName(ShaderFeature::RenderTarget2) ); + MultiLine * meta = new MultiLine; + if ( !material ) + { + // create color var + material = new Var; + material->setType( "fragout" ); + material->setName( getOutputTargetVarName(ShaderFeature::RenderTarget2) ); + material->setStructName( "OUT" ); + } + + // create texture var + Var *specularMap = new Var; + specularMap->setType( "sampler2D" ); + specularMap->setName( "specularMap" ); + specularMap->uniform = true; + specularMap->sampler = true; + specularMap->constNum = Var::getTexUnitNum(); + LangElement *texOp = new GenOp( "tex2D(@, @)", specularMap, texCoord ); + + //matinfo.g slot reserved for AO later + meta->addStatement(new GenOp(" @.g = 1.0;\r\n", material)); + meta->addStatement(new GenOp(" @.b = dot(tex2D(@, @).rgb, float3(0.3, 0.59, 0.11));\r\n", material, specularMap, texCoord)); + meta->addStatement(new GenOp(" @.a = tex2D(@, @).a;\r\n", material, specularMap, texCoord)); + output = meta; +} + +ShaderFeature::Resources DeferredSpecMapHLSL::getResources( const MaterialFeatureData &fd ) +{ + Resources res; + res.numTex = 1; + res.numTexReg = 1; + + return res; +} + +void DeferredSpecMapHLSL::setTexData( Material::StageData &stageDat, + const MaterialFeatureData &fd, + RenderPassData &passData, + U32 &texIndex ) +{ + GFXTextureObject *tex = stageDat.getTex( MFT_SpecularMap ); + if ( tex ) + { + passData.mTexType[ texIndex ] = Material::Standard; + passData.mSamplerNames[ texIndex ] = "specularMap"; + passData.mTexSlot[ texIndex++ ].texObject = tex; + } +} + +void DeferredSpecMapHLSL::processVert( Vector &componentList, + const MaterialFeatureData &fd ) +{ + MultiLine *meta = new MultiLine; + getOutTexCoord( "texCoord", + "float2", + true, + fd.features[MFT_TexAnim], + meta, + componentList ); + output = meta; +} + +// Material Info Flags -> Red ( Flags ) of Material Info Buffer. +void DeferredMatInfoFlagsHLSL::processPix( Vector &componentList, const MaterialFeatureData &fd ) +{ + // search for material var + Var *material = (Var*) LangElement::find( getOutputTargetVarName(ShaderFeature::RenderTarget2) ); + if ( !material ) + { + // create material var + material = new Var; + material->setType( "fragout" ); + material->setName( getOutputTargetVarName(ShaderFeature::RenderTarget2) ); + material->setStructName( "OUT" ); + } + + Var *matInfoFlags = new Var; + matInfoFlags->setType( "float" ); + matInfoFlags->setName( "matInfoFlags" ); + matInfoFlags->uniform = true; + matInfoFlags->constSortPos = cspPotentialPrimitive; + + output = new GenOp( " @.r = @;\r\n", material, matInfoFlags ); +} + +// Spec Strength -> Blue Channel of Material Info Buffer. +// Spec Power -> Alpha Channel ( of Material Info Buffer. +void DeferredSpecVarsHLSL::processPix( Vector &componentList, const MaterialFeatureData &fd ) +{ + // search for material var + Var *material = (Var*) LangElement::find( getOutputTargetVarName(ShaderFeature::RenderTarget2) ); + if ( !material ) + { + // create material var + material = new Var; + material->setType( "fragout" ); + material->setName( getOutputTargetVarName(ShaderFeature::RenderTarget2) ); + material->setStructName( "OUT" ); + } + + Var *specStrength = new Var; + specStrength->setType( "float" ); + specStrength->setName( "specularStrength" ); + specStrength->uniform = true; + specStrength->constSortPos = cspPotentialPrimitive; + + Var *specPower = new Var; + specPower->setType( "float" ); + specPower->setName( "specularPower" ); + specPower->uniform = true; + specPower->constSortPos = cspPotentialPrimitive; + + MultiLine * meta = new MultiLine; + //matinfo.g slot reserved for AO later + meta->addStatement(new GenOp(" @.g = 1.0;\r\n", material)); + meta->addStatement(new GenOp(" @.b = @/128;\r\n", material, specStrength)); + meta->addStatement(new GenOp(" @.a = @/5;\r\n", material, specPower)); + output = meta; +} + +// Black -> Blue and Alpha of matinfo Buffer (representing no specular), White->G (representing No AO) +void DeferredEmptySpecHLSL::processPix( Vector &componentList, const MaterialFeatureData &fd ) +{ + // search for material var + Var *material = (Var*)LangElement::find(getOutputTargetVarName(ShaderFeature::RenderTarget2)); + if (!material) + { + // create color var + material = new Var; + material->setType("fragout"); + material->setName(getOutputTargetVarName(ShaderFeature::RenderTarget2)); + material->setStructName("OUT"); + } + + MultiLine * meta = new MultiLine; + //matinfo.g slot reserved for AO later + meta->addStatement(new GenOp(" @.g = 1.0;\r\n", material)); + meta->addStatement(new GenOp(" @.ba = 0.0;\r\n", material)); + output = meta; +} diff --git a/Engine/source/lighting/advanced/hlsl/deferredShadingFeaturesHLSL.h b/Engine/source/lighting/advanced/hlsl/deferredShadingFeaturesHLSL.h new file mode 100644 index 000000000..9f43321b7 --- /dev/null +++ b/Engine/source/lighting/advanced/hlsl/deferredShadingFeaturesHLSL.h @@ -0,0 +1,84 @@ +//----------------------------------------------------------------------------- +// Copyright (c) 2012 GarageGames, LLC +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. +//----------------------------------------------------------------------------- + +#ifndef _DEFERREDSHADINGHLSL_H_ +#define _DEFERREDSHADINGHLSL_H_ + +#include "shaderGen/HLSL/shaderFeatureHLSL.h" +#include "shaderGen/HLSL/bumpHLSL.h" +#include "shaderGen/HLSL/pixSpecularHLSL.h" + +// Specular Outputs +class DeferredSpecMapHLSL : public ShaderFeatureHLSL +{ +public: + virtual String getName() { return "Deferred Shading: Specular Map"; } + + virtual void processPix( Vector &componentList, + const MaterialFeatureData &fd ); + + virtual Resources getResources( const MaterialFeatureData &fd ); + + // Sets textures and texture flags for current pass + virtual void setTexData( Material::StageData &stageDat, + const MaterialFeatureData &fd, + RenderPassData &passData, + U32 &texIndex ); + + virtual void processVert( Vector &componentList, + const MaterialFeatureData &fd ); +}; + +class DeferredMatInfoFlagsHLSL : public ShaderFeatureHLSL +{ +public: + virtual String getName() { return "Deferred Shading: Mat Info Flags"; } + + virtual void processPix( Vector &componentList, + const MaterialFeatureData &fd ); + + virtual U32 getOutputTargets( const MaterialFeatureData &fd ) const { return ShaderFeature::RenderTarget2; } +}; + +class DeferredSpecVarsHLSL : public ShaderFeatureHLSL +{ +public: + virtual String getName() { return "Deferred Shading: Specular Explicit Numbers"; } + + virtual void processPix( Vector &componentList, + const MaterialFeatureData &fd ); + + virtual U32 getOutputTargets( const MaterialFeatureData &fd ) const { return ShaderFeature::RenderTarget2; } +}; + +class DeferredEmptySpecHLSL : public ShaderFeatureHLSL +{ +public: + virtual String getName() { return "Deferred Shading: Empty Specular"; } + + virtual void processPix( Vector &componentList, + const MaterialFeatureData &fd ); + + virtual U32 getOutputTargets( const MaterialFeatureData &fd ) const { return ShaderFeature::RenderTarget2; } +}; + +#endif \ No newline at end of file diff --git a/Engine/source/materials/materialFeatureTypes.cpp b/Engine/source/materials/materialFeatureTypes.cpp index 9cdc57415..8ea7fbe8c 100644 --- a/Engine/source/materials/materialFeatureTypes.cpp +++ b/Engine/source/materials/materialFeatureTypes.cpp @@ -56,10 +56,9 @@ ImplementFeatureType( MFT_LightMap, MFG_Lighting, 4.0f, true ); ImplementFeatureType( MFT_ToneMap, MFG_Lighting, 5.0f, true ); ImplementFeatureType( MFT_VertLitTone, MFG_Lighting, 6.0f, false ); ImplementFeatureType( MFT_VertLit, MFG_Lighting, 7.0f, true ); -ImplementFeatureType( MFT_EnvMap, MFG_Lighting, 8.0f, true ); -ImplementFeatureType( MFT_CubeMap, MFG_Lighting, 9.0f, true ); -ImplementFeatureType( MFT_PixSpecular, MFG_Lighting, 10.0f, true ); -ImplementFeatureType( MFT_MinnaertShading, MFG_Lighting, 12.0f, true ); +ImplementFeatureType( MFT_PixSpecular, MFG_Lighting, 9.0f, true ); +ImplementFeatureType( MFT_MinnaertShading, MFG_Lighting, 10.0f, true ); +ImplementFeatureType( MFT_CubeMap, MFG_Lighting, 11.0f, true ); ImplementFeatureType( MFT_GlowMask, MFG_PostLighting, 1.0f, true ); ImplementFeatureType( MFT_Visibility, MFG_PostLighting, 2.0f, true ); @@ -85,6 +84,8 @@ ImplementFeatureType( MFT_NormalsOut, MFG_PreLighting, 1.0f, false ); ImplementFeatureType( MFT_LightbufferMRT, MFG_PreLighting, 1.0f, false ); ImplementFeatureType( MFT_RenderTarget1_Zero, MFG_PreTexture, 1.0f, false ); +ImplementFeatureType( MFT_RenderTarget2_Zero, MFG_PreTexture, 1.0f, false ); +ImplementFeatureType( MFT_RenderTarget3_Zero, MFG_PreTexture, 1.0f, false ); ImplementFeatureType( MFT_Foliage, MFG_PreTransform, 1.0f, false ); @@ -92,4 +93,13 @@ ImplementFeatureType( MFT_ParticleNormal, MFG_PreTransform, 2.0f, false ); ImplementFeatureType( MFT_ForwardShading, U32(-1), -1, true ); -ImplementFeatureType( MFT_ImposterVert, MFG_PreTransform, 1.0, false ); \ No newline at end of file +ImplementFeatureType( MFT_ImposterVert, MFG_PreTransform, 1.0, false ); + +// Deferred Shading +ImplementFeatureType( MFT_isDeferred, U32(-1), -1, true ); +ImplementFeatureType( MFT_SkyBox, MFG_Transform, 1.0f, true ); +ImplementFeatureType( MFT_DeferredEmptySpec, MFG_Texture, 8.01f, false ); + +ImplementFeatureType( MFT_DeferredSpecMap, MFG_Texture, 8.2f, false ); +ImplementFeatureType( MFT_DeferredSpecVars, MFG_Texture, 8.5f, false ); +ImplementFeatureType( MFT_DeferredMatInfoFlags, MFG_Texture, 8.7f, false ); diff --git a/Engine/source/materials/materialFeatureTypes.h b/Engine/source/materials/materialFeatureTypes.h index c302045c2..a002cd190 100644 --- a/Engine/source/materials/materialFeatureTypes.h +++ b/Engine/source/materials/materialFeatureTypes.h @@ -121,7 +121,6 @@ DeclareFeatureType( MFT_ToneMap ); DeclareFeatureType( MFT_VertLit ); DeclareFeatureType( MFT_VertLitTone ); -DeclareFeatureType( MFT_EnvMap ); DeclareFeatureType( MFT_CubeMap ); DeclareFeatureType( MFT_PixSpecular ); DeclareFeatureType( MFT_SpecularMap ); @@ -158,8 +157,10 @@ DeclareFeatureType( MFT_InterlacedPrePass ); /// to the second render-target DeclareFeatureType( MFT_LightbufferMRT ); -/// This feature outputs black to RenderTarget1 +/// This feature outputs black to RenderTarget3 DeclareFeatureType( MFT_RenderTarget1_Zero ); +DeclareFeatureType( MFT_RenderTarget2_Zero ); +DeclareFeatureType( MFT_RenderTarget3_Zero ); DeclareFeatureType( MFT_Foliage ); @@ -179,4 +180,11 @@ DeclareFeatureType( MFT_ForwardShading ); DeclareFeatureType( MFT_ImposterVert ); +// Deferred Shading +DeclareFeatureType( MFT_isDeferred ); +DeclareFeatureType( MFT_SkyBox ); +DeclareFeatureType( MFT_DeferredSpecMap ); +DeclareFeatureType( MFT_DeferredSpecVars ); +DeclareFeatureType( MFT_DeferredMatInfoFlags ); +DeclareFeatureType( MFT_DeferredEmptySpec ); #endif // _MATERIALFEATURETYPES_H_ diff --git a/Engine/source/shaderGen/GLSL/shaderFeatureGLSL.cpp b/Engine/source/shaderGen/GLSL/shaderFeatureGLSL.cpp index eed42dd68..ffd67b45d 100644 --- a/Engine/source/shaderGen/GLSL/shaderFeatureGLSL.cpp +++ b/Engine/source/shaderGen/GLSL/shaderFeatureGLSL.cpp @@ -156,10 +156,10 @@ LangElement *ShaderFeatureGLSL::expandNormalMap( LangElement *sampleNormalOp, } else { - // DXT Swizzle trick - meta->addStatement( new GenOp( " @ = float4( @.ag * 2.0 - 1.0, 0.0, 0.0 ); // DXTnm\r\n", normalDecl, sampleNormalOp ) ); - meta->addStatement( new GenOp( " @.z = sqrt( 1.0 - dot( @.xy, @.xy ) ); // DXTnm\r\n", normalVar, normalVar, normalVar ) ); - } + // DXT Swizzle trick + meta->addStatement( new GenOp( " @ = float4( @.ag * 2.0 - 1.0, 0.0, 0.0 ); // DXTnm\r\n", normalDecl, sampleNormalOp ) ); + meta->addStatement( new GenOp( " @.z = sqrt( 1.0 - dot( @.xy, @.xy ) ); // DXTnm\r\n", normalVar, normalVar, normalVar ) ); + } } else { @@ -708,7 +708,7 @@ void ShaderFeatureGLSL::getWsPosition( Vector &componentList, Var *objTrans = getObjTrans( componentList, useInstancing, meta ); - meta->addStatement( new GenOp( " @ = tMul( @, float4( @.xyz, 1 ) ).xyz;\r\n", + meta->addStatement( new GenOp( " @ = tMul( @, vec4( @.xyz, 1 ) ).xyz;\r\n", wsPosition, objTrans, inPosition ) ); } @@ -847,12 +847,22 @@ void DiffuseMapFeatGLSL::processVert( Vector &componentList, output = meta; } +U32 DiffuseMapFeatGLSL::getOutputTargets(const MaterialFeatureData &fd) const +{ + return fd.features[MFT_isDeferred] ? ShaderFeature::RenderTarget1 : ShaderFeature::DefaultTarget; +} + void DiffuseMapFeatGLSL::processPix( Vector &componentList, const MaterialFeatureData &fd ) { // grab connector texcoord register Var *inTex = getInTexCoord( "texCoord", "vec2", true, componentList ); + //determine output target + ShaderFeature::OutputTarget targ = ShaderFeature::DefaultTarget; + if (fd.features[MFT_isDeferred]) + targ = ShaderFeature::RenderTarget1; + // create texture var Var *diffuseMap = new Var; diffuseMap->setType( "sampler2D" ); @@ -869,7 +879,6 @@ void DiffuseMapFeatGLSL::processPix( Vector &componentList, MultiLine * meta = new MultiLine; output = meta; - if ( fd.features[MFT_CubeMap] ) { meta->addStatement( new GenOp( " @ = tex2D(@, @);\r\n", @@ -878,9 +887,8 @@ void DiffuseMapFeatGLSL::processPix( Vector &componentList, inTex ) ); if (!fd.features[MFT_Imposter]) meta->addStatement( new GenOp(" @ = toLinear(@);\r\n", diffColor, diffColor) ); - - meta->addStatement( new GenOp( " @;\r\n", assignColor( diffColor, Material::Mul ) ) ); - output = meta; + + meta->addStatement( new GenOp( " @;\r\n", assignColor( diffColor, Material::Mul, NULL, targ) ) ); } else if(fd.features[MFT_DiffuseMapAtlas]) { @@ -946,8 +954,8 @@ void DiffuseMapFeatGLSL::processPix( Vector &componentList, #ifdef DEBUG_ATLASED_UV_COORDS if(!fd.features[MFT_PrePassConditioner]) { - meta->addStatement(new GenOp(" @ = float4(@.xy, mipLod / @.w, 1.0);\r\n", new DecOp(diffColor), inTex, atParams)); - meta->addStatement(new GenOp(" @; return OUT;\r\n", assignColor(diffColor, Material::Mul))); + meta->addStatement(new GenOp(" @ = vec4(@.xy, mipLod / @.w, 1.0);\r\n", new DecOp(diffColor), inTex, atParams)); + meta->addStatement(new GenOp(" @; return OUT;\r\n", assignColor(diffColor, Material::Mul, NULL, targ) ) ); return; } #endif @@ -961,20 +969,20 @@ void DiffuseMapFeatGLSL::processPix( Vector &componentList, } else { - meta->addStatement(new GenOp( " @ = tex2D(@, @);\r\n", - new DecOp(diffColor), diffuseMap, inTex)); + meta->addStatement(new GenOp( " @ = tex2D(@, @);\r\n", + new DecOp(diffColor), diffuseMap, inTex)); if (!fd.features[MFT_Imposter]) meta->addStatement(new GenOp(" @ = toLinear(@);\r\n", diffColor, diffColor)); } - meta->addStatement(new GenOp( " @;\r\n", assignColor(diffColor, Material::Mul))); + meta->addStatement(new GenOp( " @;\r\n", assignColor(diffColor, Material::Mul, NULL, targ) ) ); } else { meta->addStatement(new GenOp("@ = tex2D(@, @);\r\n", colorDecl, diffuseMap, inTex)); if (!fd.features[MFT_Imposter]) meta->addStatement(new GenOp(" @ = toLinear(@);\r\n", diffColor, diffColor)); - meta->addStatement(new GenOp(" @;\r\n", assignColor(diffColor, Material::Mul))); + meta->addStatement(new GenOp(" @;\r\n", assignColor(diffColor, Material::Mul, NULL, targ))); } } @@ -1089,6 +1097,11 @@ void OverlayTexFeatGLSL::setTexData( Material::StageData &stageDat, // Diffuse color //**************************************************************************** +U32 DiffuseFeatureGLSL::getOutputTargets(const MaterialFeatureData &fd) const +{ + return fd.features[MFT_isDeferred] ? ShaderFeature::RenderTarget1 : ShaderFeature::DefaultTarget; +} + void DiffuseFeatureGLSL::processPix( Vector &componentList, const MaterialFeatureData &fd ) { @@ -1099,7 +1112,33 @@ void DiffuseFeatureGLSL::processPix( Vector &componentList, diffuseMaterialColor->constSortPos = cspPotentialPrimitive; MultiLine* meta = new MultiLine; - meta->addStatement( new GenOp( " @;\r\n", assignColor( diffuseMaterialColor, Material::Mul ) ) ); + Var *col = (Var*)LangElement::find("col"); + ShaderFeature::OutputTarget targ = ShaderFeature::DefaultTarget; + if (fd.features[MFT_isDeferred]) + { + targ = ShaderFeature::RenderTarget1; + + col = (Var*)LangElement::find("col1"); + MultiLine * meta = new MultiLine; + if (!col) + { + // create color var + col = new Var; + col->setType("vec4"); + col->setName(getOutputTargetVarName(targ)); + col->setStructName("OUT"); + meta->addStatement(new GenOp(" @ = vec4(1.0);\r\n", col)); + } + } + + Material::BlendOp op; + + if (fd.features[MFT_DiffuseMap]) + op = Material::Mul; + else + op = Material::None; + + meta->addStatement(new GenOp(" @;\r\n", assignColor(diffuseMaterialColor, op, NULL, targ))); output = meta; } @@ -1231,9 +1270,9 @@ void LightmapFeatGLSL::processPix( Vector &componentList, // Lightmap has already been included in the advanced light bin, so // no need to do any sampling or anything if(bPreProcessedLighting) - statement = new GenOp( "float4(@, 1.0)", inColor ); + statement = new GenOp( "vec4(@, 1.0)", inColor ); else - statement = new GenOp( "tex2D(@, @) + float4(@.rgb, 0.0)", lightMap, inTex, inColor ); + statement = new GenOp( "tex2D(@, @) + vec4(@.rgb, 0.0)", lightMap, inTex, inColor ); } } @@ -1245,8 +1284,8 @@ void LightmapFeatGLSL::processPix( Vector &componentList, MultiLine *meta = new MultiLine; if( fd.features[MFT_LightbufferMRT] ) { - meta->addStatement( new GenOp( " @;\r\n", assignColor( statement, Material::None, NULL, ShaderFeature::RenderTarget1 ) ) ); - meta->addStatement( new GenOp( " @.a = 0.0001;\r\n", LangElement::find( getOutputTargetVarName(ShaderFeature::RenderTarget1) ) ) ); + meta->addStatement( new GenOp( " @;\r\n", assignColor( statement, Material::None, NULL, ShaderFeature::RenderTarget3 ) ) ); + meta->addStatement( new GenOp( " @.a = 0.0001;\r\n", LangElement::find( getOutputTargetVarName(ShaderFeature::RenderTarget3) ) ) ); } else meta->addStatement( new GenOp( " @;\r\n", assignColor( statement, Material::Mul ) ) ); @@ -1278,7 +1317,7 @@ void LightmapFeatGLSL::setTexData( Material::StageData &stageDat, U32 LightmapFeatGLSL::getOutputTargets( const MaterialFeatureData &fd ) const { - return fd.features[MFT_LightbufferMRT] ? ShaderFeature::RenderTarget1 : ShaderFeature::DefaultTarget; + return fd.features[MFT_LightbufferMRT] ? ShaderFeature::RenderTarget3 : ShaderFeature::DefaultTarget; } //**************************************************************************** @@ -1372,8 +1411,8 @@ void TonemapFeatGLSL::processPix( Vector &componentList, // Assign to proper render target if( fd.features[MFT_LightbufferMRT] ) { - meta->addStatement( new GenOp( " @;\r\n", assignColor( toneMapColor, Material::None, NULL, ShaderFeature::RenderTarget1 ) ) ); - meta->addStatement( new GenOp( " @.a = 0.0001;\r\n", LangElement::find( getOutputTargetVarName(ShaderFeature::RenderTarget1) ) ) ); + meta->addStatement( new GenOp( " @;\r\n", assignColor( toneMapColor, Material::None, NULL, ShaderFeature::RenderTarget3 ) ) ); + meta->addStatement( new GenOp( " @.a = 0.0001;\r\n", LangElement::find( getOutputTargetVarName(ShaderFeature::RenderTarget3) ) ) ); } else meta->addStatement( new GenOp( " @;\r\n", assignColor( toneMapColor, blendOp ) ) ); @@ -1406,7 +1445,7 @@ void TonemapFeatGLSL::setTexData( Material::StageData &stageDat, U32 TonemapFeatGLSL::getOutputTargets( const MaterialFeatureData &fd ) const { - return fd.features[MFT_LightbufferMRT] ? ShaderFeature::RenderTarget1 : ShaderFeature::DefaultTarget; + return fd.features[MFT_LightbufferMRT] ? ShaderFeature::RenderTarget3 : ShaderFeature::DefaultTarget; } //**************************************************************************** @@ -1512,17 +1551,17 @@ void VertLitGLSL::processPix( Vector &componentList, // the dynamic light buffer, and it already has the baked-vertex-color // included in it if(bPreProcessedLighting) - outColor = new GenOp( "float4(@.rgb, 1.0)", rtLightingColor ); + outColor = new GenOp( "vec4(@.rgb, 1.0)", rtLightingColor ); else - outColor = new GenOp( "float4(@.rgb + @.rgb, 1.0)", rtLightingColor, outColor ); + outColor = new GenOp( "vec4(@.rgb + @.rgb, 1.0)", rtLightingColor, outColor ); } } // Output the color if ( fd.features[MFT_LightbufferMRT] ) { - meta->addStatement( new GenOp( " @;\r\n", assignColor( outColor, Material::None, NULL, ShaderFeature::RenderTarget1 ) ) ); - meta->addStatement( new GenOp( " @.a = 0.0001;\r\n", LangElement::find( getOutputTargetVarName(ShaderFeature::RenderTarget1) ) ) ); + meta->addStatement( new GenOp( " @;\r\n", assignColor( outColor, Material::None, NULL, ShaderFeature::RenderTarget3 ) ) ); + meta->addStatement( new GenOp( " @.a = 0.0001;\r\n", LangElement::find( getOutputTargetVarName(ShaderFeature::RenderTarget3) ) ) ); } else meta->addStatement( new GenOp( " @;\r\n", assignColor( outColor, blendOp ) ) ); @@ -1532,7 +1571,7 @@ void VertLitGLSL::processPix( Vector &componentList, U32 VertLitGLSL::getOutputTargets( const MaterialFeatureData &fd ) const { - return fd.features[MFT_LightbufferMRT] ? ShaderFeature::RenderTarget1 : ShaderFeature::DefaultTarget; + return fd.features[MFT_LightbufferMRT] ? ShaderFeature::RenderTarget3 : ShaderFeature::DefaultTarget; } //**************************************************************************** @@ -1571,7 +1610,10 @@ void DetailFeatGLSL::processPix( Vector &componentList, // and a simple multiplication with the detail map. LangElement *statement = new GenOp( "( tex2D(@, @) * 2.0 ) - 1.0", detailMap, inTex ); - output = new GenOp( " @;\r\n", assignColor( statement, Material::Add ) ); + if ( fd.features[MFT_isDeferred]) + output = new GenOp( " @;\r\n", assignColor( statement, Material::Add, NULL, ShaderFeature::RenderTarget1 ) ); + else + output = new GenOp( " @;\r\n", assignColor( statement, Material::Add ) ); } ShaderFeature::Resources DetailFeatGLSL::getResources( const MaterialFeatureData &fd ) @@ -1630,7 +1672,7 @@ void VertPositionGLSL::processVert( Vector &componentList, Var *modelview = getModelView( componentList, fd.features[MFT_UseInstancing], meta ); - meta->addStatement( new GenOp( " @ = tMul(@, float4(@.xyz,1));\r\n", + meta->addStatement( new GenOp( " @ = tMul(@, vec4(@.xyz,1));\r\n", outPosition, modelview, inPosition ) ); output = meta; @@ -1694,8 +1736,8 @@ void ReflectCubeFeatGLSL::processVert( Vector &componentList, cubeNormal->setName( "cubeNormal" ); cubeNormal->setType( "vec3" ); LangElement *cubeNormDecl = new DecOp( cubeNormal ); - - meta->addStatement( new GenOp( " @ = ( tMul( (@), vec4(@, 0) ) ).xyz;\r\n", + + meta->addStatement( new GenOp( " @ = ( tMul( (@), vec4(@, 0) ) ).xyz;\r\n", cubeNormDecl, cubeTrans, inNormal ) ); meta->addStatement( new GenOp( " @ = bool(length(@)) ? normalize(@) : @;\r\n", @@ -1771,9 +1813,14 @@ void ReflectCubeFeatGLSL::processPix( Vector &componentList, } else { - glossColor = (Var*) LangElement::find( "diffuseColor" ); - if( !glossColor ) - glossColor = (Var*) LangElement::find( "bumpNormal" ); + if (fd.features[MFT_isDeferred]) + glossColor = (Var*)LangElement::find(getOutputTargetVarName(ShaderFeature::RenderTarget1)); + if (!glossColor) + glossColor = (Var*)LangElement::find("specularColor"); + if (!glossColor) + glossColor = (Var*)LangElement::find("diffuseColor"); + if (!glossColor) + glossColor = (Var*)LangElement::find("bumpNormal"); } // grab connector texcoord register @@ -1786,7 +1833,7 @@ void ReflectCubeFeatGLSL::processPix( Vector &componentList, // create cubemap var Var *cubeMap = new Var; - cubeMap->setType( "samplerCUBE" ); + cubeMap->setType( "samplerCube" ); cubeMap->setName( "cubeMap" ); cubeMap->uniform = true; cubeMap->sampler = true; @@ -1800,14 +1847,36 @@ void ReflectCubeFeatGLSL::processPix( Vector &componentList, if ( fd.materialFeatures[MFT_RTLighting] ) attn =(Var*)LangElement::find("d_NL_Att"); - LangElement *texCube = new GenOp( "texCUBE( @, @ )", cubeMap, reflectVec ); + LangElement *texCube = NULL; + Var* matinfo = (Var*) LangElement::find( getOutputTargetVarName(ShaderFeature::RenderTarget2) ); + //first try and grab the gbuffer + if (fd.features[MFT_isDeferred] && matinfo) + { + + if (fd.features[MFT_DeferredSpecMap]) + texCube = new GenOp("textureLod( @, @, (@.a*5) )", cubeMap, reflectVec, matinfo); + else + texCube = new GenOp("textureLod( @, @, (@.a/4) )", cubeMap, reflectVec, matinfo); + } + else if(glossColor) //failing that, rtry and find color data + texCube = new GenOp("textureLod( @, @, @.a*5)", cubeMap, reflectVec, glossColor); + else + texCube = new GenOp("texture( @, @)", cubeMap, reflectVec); + LangElement *lerpVal = NULL; Material::BlendOp blendOp = Material::LerpAlpha; // Note that the lerpVal needs to be a float4 so that // it will work with the LerpAlpha blend. - - if ( glossColor ) + + if (matinfo) + { + if (attn) + lerpVal = new GenOp("@ * saturate( @ )", matinfo, attn); + else + lerpVal = new GenOp("@", matinfo); + } + else if ( glossColor ) { if ( attn ) lerpVal = new GenOp( "@ * saturate( @ )", glossColor, attn ); @@ -1821,8 +1890,16 @@ void ReflectCubeFeatGLSL::processPix( Vector &componentList, else blendOp = Material::Mul; } - - meta->addStatement( new GenOp( " @;\r\n", assignColor( texCube, blendOp, lerpVal ) ) ); + if (fd.features[MFT_isDeferred]) + { + Var* targ = (Var*)LangElement::find(getOutputTargetVarName(ShaderFeature::RenderTarget1)); + if (fd.features[MFT_DeferredSpecMap]) + meta->addStatement(new GenOp(" @.rgb = lerp( @.rgb, (@).rgb, (@.b));\r\n", targ, targ, texCube, lerpVal)); + else + meta->addStatement(new GenOp(" @.rgb = lerp( @.rgb, (@).rgb, (@.b*128/5));\r\n", targ, targ, texCube, lerpVal)); + } + else + meta->addStatement( new GenOp( " @;\r\n", assignColor( texCube, blendOp, lerpVal ) ) ); output = meta; } @@ -1967,7 +2044,7 @@ void RTLightingFeatGLSL::processVert( Vector &componentList, Var *objTrans = getObjTrans( componentList, fd.features[MFT_UseInstancing], meta ); // Transform the normal to world space. - meta->addStatement( new GenOp( " @ = tMul( @, float4( normalize( @ ), 0.0 ) ).xyz;\r\n", outNormal, objTrans, inNormal ) ); + meta->addStatement( new GenOp( " @ = tMul( @, vec4( normalize( @ ), 0.0 ) ).xyz;\r\n", outNormal, objTrans, inNormal ) ); } addOutWsPosition( componentList, fd.features[MFT_UseInstancing], meta ); @@ -2025,7 +2102,7 @@ void RTLightingFeatGLSL::processPix( Vector &componentList, // feature (this is done for BL terrain lightmaps). LangElement *lightMask = LangElement::find( "lightMask" ); if ( !lightMask ) - lightMask = new GenOp( "float4( 1, 1, 1, 1 )" ); + lightMask = new GenOp( "vec4( 1, 1, 1, 1 )" ); // Get all the light constants. Var *inLightPos = new Var( "inLightPos", "vec4" ); @@ -2080,7 +2157,7 @@ void RTLightingFeatGLSL::processPix( Vector &componentList, rtShading, specular ) ); // Apply the lighting to the diffuse color. - LangElement *lighting = new GenOp( "float4( @.rgb + @.rgb, 1 )", rtShading, ambient ); + LangElement *lighting = new GenOp( "vec4( @.rgb + @.rgb, 1 )", rtShading, ambient ); meta->addStatement( new GenOp( " @;\r\n", assignColor( lighting, Material::Mul ) ) ); output = meta; } @@ -2351,7 +2428,9 @@ void AlphaTestGLSL::processPix( Vector &componentList, } // If we don't have a color var then we cannot do an alpha test. - Var *color = (Var*)LangElement::find( "col" ); + Var *color = (Var*)LangElement::find( "col1" ); + if ( !color ) + color = (Var*)LangElement::find("col"); if ( !color ) { output = NULL; @@ -2718,3 +2797,16 @@ void ImposterVertFeatureGLSL::determineFeature( Material *material, outFeatureData->features.addFeature( MFT_ImposterVert ); } +//**************************************************************************** +// Vertex position +//**************************************************************************** +void DeferredSkyGLSL::processVert( Vector &componentList, + const MaterialFeatureData &fd ) +{ + Var *outPosition = (Var*)LangElement::find( "gl_Position" ); + MultiLine *meta = new MultiLine; + meta->addStatement( new GenOp( " @.w = @.z;\r\n", outPosition, outPosition ) ); + + output = meta; +} + diff --git a/Engine/source/shaderGen/GLSL/shaderFeatureGLSL.h b/Engine/source/shaderGen/GLSL/shaderFeatureGLSL.h index 276183d99..c0d5ba832 100644 --- a/Engine/source/shaderGen/GLSL/shaderFeatureGLSL.h +++ b/Engine/source/shaderGen/GLSL/shaderFeatureGLSL.h @@ -248,6 +248,8 @@ public: virtual void processPix( Vector &componentList, const MaterialFeatureData &fd ); + virtual U32 getOutputTargets(const MaterialFeatureData &fd) const; + virtual Material::BlendOp getBlendOp(){ return Material::LerpAlpha; } virtual Resources getResources( const MaterialFeatureData &fd ); @@ -301,6 +303,8 @@ public: virtual Material::BlendOp getBlendOp(){ return Material::None; } + virtual U32 getOutputTargets(const MaterialFeatureData &fd) const; + virtual String getName() { return "Diffuse Color"; @@ -656,4 +660,11 @@ public: }; +class DeferredSkyGLSL : public ShaderFeatureGLSL +{ +public: + virtual String getName() { return "Deferred Shading: Sky"; } + virtual void processVert( Vector &componentList, + const MaterialFeatureData &fd ); +}; #endif // _SHADERGEN_GLSL_SHADERFEATUREGLSL_H_ diff --git a/Engine/source/shaderGen/GLSL/shaderGenGLSL.h b/Engine/source/shaderGen/GLSL/shaderGenGLSL.h index c14402875..77305b8e7 100644 --- a/Engine/source/shaderGen/GLSL/shaderGenGLSL.h +++ b/Engine/source/shaderGen/GLSL/shaderGenGLSL.h @@ -30,8 +30,11 @@ class ShaderGenPrinterGLSL : public ShaderGenPrinter { + bool extraRTs[3]; + public: - + ShaderGenPrinterGLSL() { for (int i = 0; i < 3; i++) extraRTs[i] = false; } + // ShaderGenPrinter virtual void printShaderHeader(Stream& stream); virtual void printMainComment(Stream& stream); diff --git a/Engine/source/shaderGen/GLSL/shaderGenGLSLInit.cpp b/Engine/source/shaderGen/GLSL/shaderGenGLSLInit.cpp index ef15793a5..020b68d43 100644 --- a/Engine/source/shaderGen/GLSL/shaderGenGLSLInit.cpp +++ b/Engine/source/shaderGen/GLSL/shaderGenGLSLInit.cpp @@ -34,6 +34,8 @@ #include "core/module.h" #include "shaderGen/GLSL/accuFeatureGLSL.h" +// Deferred Shading +#include "lighting/advanced/glsl/deferredShadingFeaturesGLSL.h" static ShaderGen::ShaderGenInitDelegate sInitDelegate; @@ -66,8 +68,13 @@ void _initShaderGenGLSL( ShaderGen *shaderGen ) FEATUREMGR->registerFeature( MFT_AccuMap, new AccuTexFeatGLSL ); FEATUREMGR->registerFeature( MFT_GlossMap, new NamedFeatureGLSL( "Gloss Map" ) ); FEATUREMGR->registerFeature( MFT_IsTranslucent, new NamedFeatureGLSL( "Translucent" ) ); + FEATUREMGR->registerFeature( MFT_IsTranslucentZWrite, new NamedFeatureGLSL( "Translucent ZWrite" ) ); FEATUREMGR->registerFeature( MFT_Visibility, new VisibilityFeatGLSL ); FEATUREMGR->registerFeature( MFT_Fog, new FogFeatGLSL ); + FEATUREMGR->registerFeature( MFT_LightbufferMRT, new NamedFeatureGLSL( "Lightbuffer MRT" ) ); + FEATUREMGR->registerFeature( MFT_RenderTarget1_Zero, new RenderTargetZeroGLSL( ShaderFeature::RenderTarget1 ) ); + FEATUREMGR->registerFeature( MFT_RenderTarget2_Zero, new RenderTargetZeroGLSL( ShaderFeature::RenderTarget2 ) ); + FEATUREMGR->registerFeature( MFT_RenderTarget3_Zero, new RenderTargetZeroGLSL( ShaderFeature::RenderTarget3 ) ); FEATUREMGR->registerFeature( MFT_Imposter, new NamedFeatureGLSL( "Imposter" ) ); FEATUREMGR->registerFeature( MFT_NormalsOut, new NormalsOutFeatGLSL ); @@ -80,9 +87,6 @@ void _initShaderGenGLSL( ShaderGen *shaderGen ) FEATUREMGR->registerFeature( MFT_ParaboloidVertTransform, new ParaboloidVertTransformGLSL ); FEATUREMGR->registerFeature( MFT_IsSinglePassParaboloid, new NamedFeatureGLSL( "Single Pass Paraboloid" ) ); FEATUREMGR->registerFeature( MFT_UseInstancing, new NamedFeatureGLSL( "Hardware Instancing" ) ); - - FEATUREMGR->registerFeature( MFT_RenderTarget1_Zero, new RenderTargetZeroGLSL - ( ShaderFeature::RenderTarget1 ) ); FEATUREMGR->registerFeature( MFT_DiffuseMapAtlas, new NamedFeatureGLSL( "Diffuse Map Atlas" ) ); FEATUREMGR->registerFeature( MFT_NormalMapAtlas, new NamedFeatureGLSL( "Normal Map Atlas" ) ); @@ -94,10 +98,13 @@ void _initShaderGenGLSL( ShaderGen *shaderGen ) FEATUREMGR->registerFeature( MFT_ImposterVert, new ImposterVertFeatureGLSL ); - FEATUREMGR->registerFeature( MFT_LightbufferMRT, new NamedFeatureGLSL( "Lightbuffer MRT" ) ); - //FEATUREMGR->registerFeature( MFT_IsTranslucentZWrite, new NamedFeatureGLSL( "Translucent ZWrite" ) ); - //FEATUREMGR->registerFeature( MFT_InterlacedPrePass, new NamedFeatureGLSL( "Interlaced Pre Pass" ) ); - + // Deferred Shading + FEATUREMGR->registerFeature( MFT_isDeferred, new NamedFeatureGLSL( "Deferred Material" ) ); + FEATUREMGR->registerFeature( MFT_DeferredSpecMap, new DeferredSpecMapGLSL ); + FEATUREMGR->registerFeature( MFT_DeferredSpecVars, new DeferredSpecVarsGLSL ); + FEATUREMGR->registerFeature( MFT_DeferredMatInfoFlags, new DeferredMatInfoFlagsGLSL ); + FEATUREMGR->registerFeature( MFT_DeferredEmptySpec, new DeferredEmptySpecGLSL ); + FEATUREMGR->registerFeature( MFT_SkyBox, new DeferredSkyGLSL ); } MODULE_BEGIN( ShaderGenGLSL ) diff --git a/Engine/source/shaderGen/HLSL/shaderFeatureHLSL.cpp b/Engine/source/shaderGen/HLSL/shaderFeatureHLSL.cpp index 17cf6333c..59264c3a9 100644 --- a/Engine/source/shaderGen/HLSL/shaderFeatureHLSL.cpp +++ b/Engine/source/shaderGen/HLSL/shaderFeatureHLSL.cpp @@ -845,12 +845,22 @@ void DiffuseMapFeatHLSL::processVert( Vector &componentList, output = meta; } +U32 DiffuseMapFeatHLSL::getOutputTargets(const MaterialFeatureData &fd) const +{ + return fd.features[MFT_isDeferred] ? ShaderFeature::RenderTarget1 : ShaderFeature::DefaultTarget; +} + void DiffuseMapFeatHLSL::processPix( Vector &componentList, const MaterialFeatureData &fd ) { // grab connector texcoord register Var *inTex = getInTexCoord( "texCoord", "float2", true, componentList ); + //determine output target + ShaderFeature::OutputTarget targ = ShaderFeature::DefaultTarget; + if (fd.features[MFT_isDeferred]) + targ = ShaderFeature::RenderTarget1; + // create texture var Var *diffuseMap = new Var; diffuseMap->setType( "sampler2D" ); @@ -877,7 +887,7 @@ void DiffuseMapFeatHLSL::processPix( Vector &componentList, if (!fd.features[MFT_Imposter]) meta->addStatement(new GenOp(" @ = toLinear(@);\r\n", diffColor, diffColor)); - meta->addStatement( new GenOp( " @;\r\n", assignColor( diffColor, Material::Mul ) ) ); + meta->addStatement(new GenOp(" @;\r\n", assignColor(diffColor, Material::Mul, NULL, targ))); } else if(fd.features[MFT_DiffuseMapAtlas]) { @@ -944,7 +954,7 @@ void DiffuseMapFeatHLSL::processPix( Vector &componentList, if(!fd.features[MFT_PrePassConditioner]) { meta->addStatement(new GenOp(" @ = float4(@.xy, mipLod / @.w, 1.0);\r\n", new DecOp(diffColor), inTex, atParams)); - meta->addStatement(new GenOp(" @; return OUT;\r\n", assignColor(diffColor, Material::Mul))); + meta->addStatement(new GenOp(" @; return OUT;\r\n", assignColor(diffColor, Material::Mul, NULL, targ) ) ); return; } #endif @@ -962,15 +972,15 @@ void DiffuseMapFeatHLSL::processPix( Vector &componentList, if (!fd.features[MFT_Imposter]) meta->addStatement(new GenOp(" @ = toLinear(@);\r\n", diffColor, diffColor)); - meta->addStatement(new GenOp( " @;\r\n", assignColor(diffColor, Material::Mul))); + meta->addStatement(new GenOp(" @;\r\n", assignColor(diffColor, Material::Mul, NULL, targ) ) ); } else { meta->addStatement(new GenOp("@ = tex2D(@, @);\r\n", colorDecl, diffuseMap, inTex)); if (!fd.features[MFT_Imposter]) meta->addStatement(new GenOp(" @ = toLinear(@);\r\n", diffColor, diffColor)); - meta->addStatement(new GenOp(" @;\r\n", assignColor(diffColor, Material::Mul))); - } + meta->addStatement(new GenOp(" @;\r\n", assignColor(diffColor, Material::Mul, NULL, targ))); + } } ShaderFeature::Resources DiffuseMapFeatHLSL::getResources( const MaterialFeatureData &fd ) @@ -1087,6 +1097,11 @@ void OverlayTexFeatHLSL::setTexData( Material::StageData &stageDat, // Diffuse color //**************************************************************************** +U32 DiffuseFeatureHLSL::getOutputTargets(const MaterialFeatureData &fd) const +{ + return fd.features[MFT_isDeferred] ? ShaderFeature::RenderTarget1 : ShaderFeature::DefaultTarget; +} + void DiffuseFeatureHLSL::processPix( Vector &componentList, const MaterialFeatureData &fd ) { @@ -1096,8 +1111,34 @@ void DiffuseFeatureHLSL::processPix( Vector &componentList, diffuseMaterialColor->uniform = true; diffuseMaterialColor->constSortPos = cspPotentialPrimitive; - MultiLine * meta = new MultiLine; - meta->addStatement( new GenOp( " @;\r\n", assignColor( diffuseMaterialColor, Material::Mul ) ) ); + MultiLine* meta = new MultiLine; + Var *col = (Var*)LangElement::find("col"); + ShaderFeature::OutputTarget targ = ShaderFeature::DefaultTarget; + if (fd.features[MFT_isDeferred]) + { + targ = ShaderFeature::RenderTarget1; + + col = (Var*)LangElement::find("col1"); + MultiLine * meta = new MultiLine; + if (!col) + { + // create color var + col = new Var; + col->setType("fragout"); + col->setName(getOutputTargetVarName(targ)); + col->setStructName("OUT"); + meta->addStatement(new GenOp(" @ = float4(1.0);\r\n", col)); + } + } + + Material::BlendOp op; + + if (fd.features[MFT_DiffuseMap]) + op = Material::Mul; + else + op = Material::None; + + meta->addStatement( new GenOp( " @;\r\n", assignColor( diffuseMaterialColor, op, NULL, targ ) ) ); output = meta; } @@ -1243,8 +1284,8 @@ void LightmapFeatHLSL::processPix( Vector &componentList, MultiLine *meta = new MultiLine; if( fd.features[MFT_LightbufferMRT] ) { - meta->addStatement( new GenOp( " @;\r\n", assignColor( statement, Material::None, NULL, ShaderFeature::RenderTarget1 ) ) ); - meta->addStatement( new GenOp( " @.a = 0.0001;\r\n", LangElement::find( getOutputTargetVarName(ShaderFeature::RenderTarget1) ) ) ); + meta->addStatement( new GenOp( " @;\r\n", assignColor( statement, Material::None, NULL, ShaderFeature::RenderTarget3 ) ) ); + meta->addStatement( new GenOp( " @.a = 0.0001;\r\n", LangElement::find( getOutputTargetVarName(ShaderFeature::RenderTarget3) ) ) ); } else meta->addStatement( new GenOp( " @;\r\n", assignColor( statement, Material::Mul ) ) ); @@ -1276,7 +1317,7 @@ void LightmapFeatHLSL::setTexData( Material::StageData &stageDat, U32 LightmapFeatHLSL::getOutputTargets( const MaterialFeatureData &fd ) const { - return fd.features[MFT_LightbufferMRT] ? ShaderFeature::RenderTarget1 : ShaderFeature::DefaultTarget; + return fd.features[MFT_LightbufferMRT] ? ShaderFeature::RenderTarget3 : ShaderFeature::DefaultTarget; } //**************************************************************************** @@ -1370,8 +1411,8 @@ void TonemapFeatHLSL::processPix( Vector &componentList, // Assign to proper render target if( fd.features[MFT_LightbufferMRT] ) { - meta->addStatement( new GenOp( " @;\r\n", assignColor( toneMapColor, Material::None, NULL, ShaderFeature::RenderTarget1 ) ) ); - meta->addStatement( new GenOp( " @.a = 0.0001;\r\n", LangElement::find( getOutputTargetVarName(ShaderFeature::RenderTarget1) ) ) ); + meta->addStatement( new GenOp( " @;\r\n", assignColor( toneMapColor, Material::None, NULL, ShaderFeature::RenderTarget3 ) ) ); + meta->addStatement( new GenOp( " @.a = 0.0001;\r\n", LangElement::find( getOutputTargetVarName(ShaderFeature::RenderTarget3) ) ) ); } else meta->addStatement( new GenOp( " @;\r\n", assignColor( toneMapColor, blendOp ) ) ); @@ -1404,7 +1445,7 @@ void TonemapFeatHLSL::setTexData( Material::StageData &stageDat, U32 TonemapFeatHLSL::getOutputTargets( const MaterialFeatureData &fd ) const { - return fd.features[MFT_LightbufferMRT] ? ShaderFeature::RenderTarget1 : ShaderFeature::DefaultTarget; + return fd.features[MFT_LightbufferMRT] ? ShaderFeature::RenderTarget3 : ShaderFeature::DefaultTarget; } //**************************************************************************** @@ -1519,8 +1560,8 @@ void VertLitHLSL::processPix( Vector &componentList, // Output the color if ( fd.features[MFT_LightbufferMRT] ) { - meta->addStatement( new GenOp( " @;\r\n", assignColor( outColor, Material::None, NULL, ShaderFeature::RenderTarget1 ) ) ); - meta->addStatement( new GenOp( " @.a = 0.0001;\r\n", LangElement::find( getOutputTargetVarName(ShaderFeature::RenderTarget1) ) ) ); + meta->addStatement( new GenOp( " @;\r\n", assignColor( outColor, Material::None, NULL, ShaderFeature::RenderTarget3 ) ) ); + meta->addStatement( new GenOp( " @.a = 0.0001;\r\n", LangElement::find( getOutputTargetVarName(ShaderFeature::RenderTarget3) ) ) ); } else meta->addStatement( new GenOp( " @;\r\n", assignColor( outColor, blendOp ) ) ); @@ -1530,7 +1571,7 @@ void VertLitHLSL::processPix( Vector &componentList, U32 VertLitHLSL::getOutputTargets( const MaterialFeatureData &fd ) const { - return fd.features[MFT_LightbufferMRT] ? ShaderFeature::RenderTarget1 : ShaderFeature::DefaultTarget; + return fd.features[MFT_LightbufferMRT] ? ShaderFeature::RenderTarget3 : ShaderFeature::DefaultTarget; } //**************************************************************************** @@ -1569,7 +1610,10 @@ void DetailFeatHLSL::processPix( Vector &componentList, // and a simple multiplication with the detail map. LangElement *statement = new GenOp( "( tex2D(@, @) * 2.0 ) - 1.0", detailMap, inTex ); - output = new GenOp( " @;\r\n", assignColor( statement, Material::Add ) ); + if ( fd.features[MFT_isDeferred]) + output = new GenOp( " @;\r\n", assignColor( statement, Material::Add, NULL, ShaderFeature::RenderTarget1 ) ); + else + output = new GenOp( " @;\r\n", assignColor( statement, Material::Add ) ); } ShaderFeature::Resources DetailFeatHLSL::getResources( const MaterialFeatureData &fd ) @@ -1694,7 +1738,7 @@ void ReflectCubeFeatHLSL::processVert( Vector &componentList, cubeNormal->setType( "float3" ); LangElement *cubeNormDecl = new DecOp( cubeNormal ); - meta->addStatement( new GenOp( " @ = normalize( mul(@, float4(normalize(@),0.0)).xyz );\r\n", + meta->addStatement( new GenOp( " @ = normalize( mul(@, float4(normalize(@),0.0)).xyz );\r\n", cubeNormDecl, cubeTrans, inNormal ) ); // grab the eye position @@ -1767,9 +1811,14 @@ void ReflectCubeFeatHLSL::processPix( Vector &componentList, } else { - glossColor = (Var*) LangElement::find( "diffuseColor" ); - if( !glossColor ) - glossColor = (Var*) LangElement::find( "bumpNormal" ); + if (fd.features[MFT_isDeferred]) + glossColor = (Var*)LangElement::find(getOutputTargetVarName(ShaderFeature::RenderTarget1)); + if (!glossColor) + glossColor = (Var*)LangElement::find("specularColor"); + if (!glossColor) + glossColor = (Var*)LangElement::find("diffuseColor"); + if (!glossColor) + glossColor = (Var*)LangElement::find("bumpNormal"); } // grab connector texcoord register @@ -1795,15 +1844,41 @@ void ReflectCubeFeatHLSL::processPix( Vector &componentList, //else if ( fd.materialFeatures[MFT_RTLighting] ) attn =(Var*)LangElement::find("d_NL_Att"); + + LangElement *texCube = NULL; + Var* matinfo = (Var*) LangElement::find( getOutputTargetVarName(ShaderFeature::RenderTarget2) ); + //first try and grab the gbuffer + if (fd.features[MFT_isDeferred] && matinfo) + { + // Cube LOD level = (1.0 - Roughness) * 8 + // mip_levle = min((1.0 - u_glossiness)*11.0 + 1.0, 8.0) + //LangElement *texCube = new GenOp( "texCUBElod( @, float4(@, min((1.0 - (@ / 128.0)) * 11.0 + 1.0, 8.0)) )", cubeMap, reflectVec, specPower ); + + if (fd.features[MFT_DeferredSpecMap]) + texCube = new GenOp("texCUBElod( @, float4(@, (@.a*5)) )", cubeMap, reflectVec, matinfo); + else + texCube = new GenOp("texCUBElod( @, float4(@, (@.a/4)) )", cubeMap, reflectVec, matinfo); + } + else + if (glossColor) //failing that, rtry and find color data + texCube = new GenOp("texCUBElod( @, float4(@, @.a*5))", cubeMap, reflectVec, glossColor); + else //failing *that*, just draw the cubemap + texCube = new GenOp("texCUBE( @, @)", cubeMap, reflectVec); - LangElement *texCube = new GenOp( "texCUBE( @, @ )", cubeMap, reflectVec ); LangElement *lerpVal = NULL; Material::BlendOp blendOp = Material::LerpAlpha; // Note that the lerpVal needs to be a float4 so that // it will work with the LerpAlpha blend. - if ( glossColor ) + if (matinfo) + { + if (attn) + lerpVal = new GenOp("@ * saturate( @ )", matinfo, attn); + else + lerpVal = new GenOp("@", matinfo); + } + else if ( glossColor ) { if ( attn ) lerpVal = new GenOp( "@ * saturate( @ )", glossColor, attn ); @@ -1817,8 +1892,16 @@ void ReflectCubeFeatHLSL::processPix( Vector &componentList, else blendOp = Material::Mul; } - - meta->addStatement( new GenOp( " @;\r\n", assignColor( texCube, blendOp, lerpVal ) ) ); + if (fd.features[MFT_isDeferred]) + { + Var* targ = (Var*)LangElement::find(getOutputTargetVarName(ShaderFeature::RenderTarget1)); + if (fd.features[MFT_DeferredSpecMap]) + meta->addStatement(new GenOp(" @.rgb = lerp( @.rgb, (@).rgb, (@.b));\r\n", targ, targ, texCube, lerpVal)); + else + meta->addStatement(new GenOp(" @.rgb = lerp( @.rgb, (@).rgb, (@.b*128/5));\r\n", targ, targ, texCube, lerpVal)); + } + else + meta->addStatement( new GenOp( " @;\r\n", assignColor( texCube, blendOp, lerpVal ) ) ); output = meta; } @@ -1865,9 +1948,9 @@ void ReflectCubeFeatHLSL::setTexData( Material::StageData &stageDat, { passData.mSamplerNames[ texIndex ] = "bumpMap"; passData.mTexSlot[ texIndex++ ].texObject = tex; + } } } - } if( stageDat.getCubemap() ) { @@ -2347,7 +2430,9 @@ void AlphaTestHLSL::processPix( Vector &componentList, } // If we don't have a color var then we cannot do an alpha test. - Var *color = (Var*)LangElement::find( "col" ); + Var *color = (Var*)LangElement::find( "col1" ); + if (!color) + color = (Var*)LangElement::find("col"); if ( !color ) { output = NULL; @@ -2714,3 +2799,16 @@ void ImposterVertFeatureHLSL::determineFeature( Material *material, outFeatureData->features.addFeature( MFT_ImposterVert ); } + +//**************************************************************************** +// Vertex position +//**************************************************************************** +void DeferredSkyHLSL::processVert( Vector &componentList, + const MaterialFeatureData &fd ) +{ + Var *outPosition = (Var*)LangElement::find( "hpos" ); + MultiLine *meta = new MultiLine; + meta->addStatement( new GenOp( " @.w = @.z;\r\n", outPosition, outPosition ) ); + + output = meta; +} diff --git a/Engine/source/shaderGen/HLSL/shaderFeatureHLSL.h b/Engine/source/shaderGen/HLSL/shaderFeatureHLSL.h index 4f77c175a..b09d4d561 100644 --- a/Engine/source/shaderGen/HLSL/shaderFeatureHLSL.h +++ b/Engine/source/shaderGen/HLSL/shaderFeatureHLSL.h @@ -248,6 +248,8 @@ public: virtual void processPix( Vector &componentList, const MaterialFeatureData &fd ); + virtual U32 getOutputTargets(const MaterialFeatureData &fd) const; + virtual Material::BlendOp getBlendOp(){ return Material::LerpAlpha; } virtual Resources getResources( const MaterialFeatureData &fd ); @@ -301,6 +303,7 @@ public: virtual Material::BlendOp getBlendOp(){ return Material::None; } + virtual U32 getOutputTargets(const MaterialFeatureData &fd) const; virtual String getName() { return "Diffuse Color"; @@ -656,4 +659,12 @@ public: }; +class DeferredSkyHLSL : public ShaderFeatureHLSL +{ +public: + virtual String getName() { return "Deferred Shading: Sky"; } + virtual void processVert( Vector &componentList, + const MaterialFeatureData &fd ); +}; + #endif // _SHADERGEN_HLSL_SHADERFEATUREHLSL_H_ diff --git a/Engine/source/shaderGen/HLSL/shaderGenHLSLInit.cpp b/Engine/source/shaderGen/HLSL/shaderGenHLSLInit.cpp index a5c6165e0..fd30656bd 100644 --- a/Engine/source/shaderGen/HLSL/shaderGenHLSLInit.cpp +++ b/Engine/source/shaderGen/HLSL/shaderGenHLSLInit.cpp @@ -32,6 +32,8 @@ #include "shaderGen/HLSL/paraboloidHLSL.h" #include "materials/materialFeatureTypes.h" #include "core/module.h" +// Deferred Shading +#include "lighting/advanced/hlsl/deferredShadingFeaturesHLSL.h" #include "shaderGen/HLSL/accuFeatureHLSL.h" static ShaderGen::ShaderGenInitDelegate sInitDelegate; @@ -70,6 +72,8 @@ void _initShaderGenHLSL( ShaderGen *shaderGen ) FEATUREMGR->registerFeature( MFT_GlossMap, new NamedFeatureHLSL( "Gloss Map" ) ); FEATUREMGR->registerFeature( MFT_LightbufferMRT, new NamedFeatureHLSL( "Lightbuffer MRT" ) ); FEATUREMGR->registerFeature( MFT_RenderTarget1_Zero, new RenderTargetZeroHLSL( ShaderFeature::RenderTarget1 ) ); + FEATUREMGR->registerFeature( MFT_RenderTarget2_Zero, new RenderTargetZeroHLSL( ShaderFeature::RenderTarget2 ) ); + FEATUREMGR->registerFeature( MFT_RenderTarget3_Zero, new RenderTargetZeroHLSL( ShaderFeature::RenderTarget3 ) ); FEATUREMGR->registerFeature( MFT_Imposter, new NamedFeatureHLSL( "Imposter" ) ); FEATUREMGR->registerFeature( MFT_DiffuseMapAtlas, new NamedFeatureHLSL( "Diffuse Map Atlas" ) ); @@ -95,6 +99,14 @@ void _initShaderGenHLSL( ShaderGen *shaderGen ) FEATUREMGR->registerFeature( MFT_ForwardShading, new NamedFeatureHLSL( "Forward Shaded Material" ) ); FEATUREMGR->registerFeature( MFT_ImposterVert, new ImposterVertFeatureHLSL ); + + // Deferred Shading + FEATUREMGR->registerFeature( MFT_isDeferred, new NamedFeatureHLSL( "Deferred Material" ) ); + FEATUREMGR->registerFeature( MFT_DeferredSpecMap, new DeferredSpecMapHLSL ); + FEATUREMGR->registerFeature( MFT_DeferredSpecVars, new DeferredSpecVarsHLSL ); + FEATUREMGR->registerFeature( MFT_DeferredMatInfoFlags, new DeferredMatInfoFlagsHLSL ); + FEATUREMGR->registerFeature( MFT_DeferredEmptySpec, new DeferredEmptySpecHLSL ); + FEATUREMGR->registerFeature( MFT_SkyBox, new DeferredSkyHLSL ); } MODULE_BEGIN( ShaderGenHLSL ) diff --git a/Engine/source/shaderGen/shaderFeature.cpp b/Engine/source/shaderGen/shaderFeature.cpp index 9c1d8d955..7e22f72ad 100644 --- a/Engine/source/shaderGen/shaderFeature.cpp +++ b/Engine/source/shaderGen/shaderFeature.cpp @@ -47,10 +47,24 @@ ShaderFeature::Resources ShaderFeature::getResources( const MaterialFeatureData const char* ShaderFeature::getOutputTargetVarName( OutputTarget target ) const { const char* targName = "col"; - if ( target != DefaultTarget ) + + switch(target) { + case DefaultTarget: + targName = "col"; + break; + + case RenderTarget1: targName = "col1"; - AssertFatal(target == RenderTarget1, "yeah Pat is lame and didn't want to do bit math stuff, TODO"); + break; + + case RenderTarget2: + targName = "col2"; + break; + + case RenderTarget3: + targName = "col3"; + break; } return targName; diff --git a/Engine/source/shaderGen/shaderGenVars.cpp b/Engine/source/shaderGen/shaderGenVars.cpp index 3e5a577ee..f4aca6cf5 100644 --- a/Engine/source/shaderGen/shaderGenVars.cpp +++ b/Engine/source/shaderGen/shaderGenVars.cpp @@ -80,4 +80,7 @@ const String ShaderGenVars::cubeMap("$cubeMap"); const String ShaderGenVars::dLightMap("$dlightMap"); const String ShaderGenVars::dLightMapSec("$dlightMapSec"); const String ShaderGenVars::dLightMask("$dlightMask"); -const String ShaderGenVars::toneMap("$toneMap"); \ No newline at end of file +const String ShaderGenVars::toneMap("$toneMap"); + +// Deferred shading +const String ShaderGenVars::matInfoFlags("$matInfoFlags"); \ No newline at end of file diff --git a/Engine/source/shaderGen/shaderGenVars.h b/Engine/source/shaderGen/shaderGenVars.h index a98940101..52ff016d0 100644 --- a/Engine/source/shaderGen/shaderGenVars.h +++ b/Engine/source/shaderGen/shaderGenVars.h @@ -94,6 +94,9 @@ struct ShaderGenVars const static String dLightMapSec; const static String dLightMask; const static String toneMap; + + // Deferred Shading + const static String matInfoFlags; }; #endif diff --git a/Engine/source/terrain/glsl/terrFeatureGLSL.cpp b/Engine/source/terrain/glsl/terrFeatureGLSL.cpp index 5f32359a8..95acfc773 100644 --- a/Engine/source/terrain/glsl/terrFeatureGLSL.cpp +++ b/Engine/source/terrain/glsl/terrFeatureGLSL.cpp @@ -26,6 +26,7 @@ #include "terrain/terrFeatureTypes.h" #include "materials/materialFeatureTypes.h" #include "materials/materialFeatureData.h" +#include "materials/processedMaterial.h" #include "gfx/gfxDevice.h" #include "shaderGen/langElement.h" #include "shaderGen/shaderOp.h" @@ -48,6 +49,10 @@ namespace FEATUREMGR->registerFeature( MFT_TerrainLightMap, new TerrainLightMapFeatGLSL ); FEATUREMGR->registerFeature( MFT_TerrainSideProject, new NamedFeatureGLSL( "Terrain Side Projection" ) ); FEATUREMGR->registerFeature( MFT_TerrainAdditive, new TerrainAdditiveFeatGLSL ); + FEATUREMGR->registerFeature( MFT_DeferredTerrainBaseMap, new TerrainBaseMapFeatGLSL ); + FEATUREMGR->registerFeature( MFT_DeferredTerrainMacroMap, new TerrainMacroMapFeatGLSL ); + FEATUREMGR->registerFeature( MFT_DeferredTerrainDetailMap, new TerrainDetailMapFeatGLSL ); + FEATUREMGR->registerFeature( MFT_DeferredTerrainBlankInfoMap, new TerrainBlankInfoMapFeatGLSL ); } }; @@ -250,10 +255,6 @@ void TerrainBaseMapFeatGLSL::processPix( Vector &componentLis // grab connector texcoord register Var *texCoord = getInTexCoord( "texCoord", "vec3", true, componentList ); - // We do nothing more if this is a prepass. - if ( fd.features.hasFeature( MFT_PrePassConditioner ) ) - return; - // create texture var Var *diffuseMap = new Var; diffuseMap->setType( "sampler2D" ); @@ -269,7 +270,14 @@ void TerrainBaseMapFeatGLSL::processPix( Vector &componentLis baseColor->setName( "baseColor" ); meta->addStatement( new GenOp( " @ = tex2D( @, @.xy );\r\n", new DecOp( baseColor ), diffuseMap, texCoord ) ); meta->addStatement(new GenOp(" @ = toLinear(@);\r\n", baseColor, baseColor)); - meta->addStatement( new GenOp( " @;\r\n", assignColor( baseColor, Material::Mul ) ) ); + + ShaderFeature::OutputTarget target = ShaderFeature::DefaultTarget; + + if(fd.features.hasFeature(MFT_isDeferred)) + { + target= ShaderFeature::RenderTarget1; + } + meta->addStatement( new GenOp( " @;\r\n", assignColor( baseColor, Material::Mul,NULL,target ) ) ); output = meta; } @@ -278,14 +286,16 @@ ShaderFeature::Resources TerrainBaseMapFeatGLSL::getResources( const MaterialFea { Resources res; res.numTexReg = 1; - - // We only sample from the base map during a diffuse pass. - if ( !fd.features.hasFeature( MFT_PrePassConditioner ) ) res.numTex = 1; return res; } +U32 TerrainBaseMapFeatGLSL::getOutputTargets( const MaterialFeatureData &fd ) const +{ + return fd.features[MFT_isDeferred] ? ShaderFeature::RenderTarget1 : ShaderFeature::DefaultTarget; +} + TerrainDetailMapFeatGLSL::TerrainDetailMapFeatGLSL() : mTorqueDep( "shaders/common/gl/torque.glsl" ), mTerrainDep( "shaders/common/terrain/terrain.glsl" ) @@ -386,6 +396,9 @@ void TerrainDetailMapFeatGLSL::processPix( Vector &component const U32 detailIndex = getProcessIndex(); Var *inTex = getVertTexCoord( "texCoord" ); + // new terrain + bool hasNormal = fd.features.hasFeature(MFT_TerrainNormalMap, detailIndex); + MultiLine *meta = new MultiLine; // We need the negative tangent space view vector @@ -454,6 +467,100 @@ void TerrainDetailMapFeatGLSL::processPix( Vector &component meta->addStatement( new GenOp( " @ = calcBlend( @.x, @.xy, @, @ );\r\n", new DecOp( detailBlend ), detailInfo, inTex, layerSize, layerSample ) ); + // New terrain + + Var *lerpBlend = (Var*)LangElement::find("lerpBlend"); + if (!lerpBlend) + { + lerpBlend = new Var; + lerpBlend->setType("float"); + lerpBlend->setName("lerpBlend"); + lerpBlend->uniform = true; + lerpBlend->constSortPos = cspPrimitive; + } + + + Var *blendDepth = (Var*)LangElement::find(String::ToString("blendDepth%d", detailIndex)); + if (!blendDepth) + { + blendDepth = new Var; + blendDepth->setType("float"); + blendDepth->setName(String::ToString("blendDepth%d", detailIndex)); + blendDepth->uniform = true; + blendDepth->constSortPos = cspPrimitive; + } + + Var *baseColor = (Var*)LangElement::find("baseColor"); + ShaderFeature::OutputTarget target = ShaderFeature::DefaultTarget; + + if(fd.features.hasFeature( MFT_DeferredTerrainDetailMap )) + target= ShaderFeature::RenderTarget1; + + Var *outColor = (Var*)LangElement::find( getOutputTargetVarName(target) ); + + if (!outColor) + { + // create color var + outColor = new Var; + outColor->setType("float4"); + outColor->setName("col"); + outColor->setStructName("OUT"); + meta->addStatement(new GenOp(" @;\r\n", outColor)); + } + + Var *detailColor = (Var*)LangElement::find("detailColor"); + if (!detailColor) + { + detailColor = new Var; + detailColor->setType("float4"); + detailColor->setName("detailColor"); + meta->addStatement(new GenOp(" @;\r\n", new DecOp(detailColor))); + } + + // Get the detail texture. + Var *detailMap = new Var; + detailMap->setType("sampler2D"); + detailMap->setName(String::ToString("detailMap%d", detailIndex)); + detailMap->uniform = true; + detailMap->sampler = true; + detailMap->constNum = Var::getTexUnitNum(); // used as texture unit num here + + // Get the normal map texture. + Var *normalMap = _getNormalMapTex(); + + // Issue happens somewhere here ----- + + // Sample the normal map. + // + // We take two normal samples and lerp between them for + // side projection layers... else a single sample. + LangElement *texOp; + + // Note that we're doing the standard greyscale detail + // map technique here which can darken and lighten the + // diffuse texture. + // + // We take two color samples and lerp between them for + // side projection layers... else a single sample. + // + if (fd.features.hasFeature(MFT_TerrainSideProject, detailIndex)) + { + meta->addStatement(new GenOp(" @ = ( lerp( tex2D( @, @.yz ), tex2D( @, @.xz ), @.z ) * 2.0 ) - 1.0;\r\n", + detailColor, detailMap, inDet, detailMap, inDet, inTex)); + + texOp = new GenOp("lerp( tex2D( @, @.yz ), tex2D( @, @.xz ), @.z )", + normalMap, inDet, normalMap, inDet, inTex); + } + else + { + meta->addStatement(new GenOp(" @ = ( tex2D( @, @.xy ) * 2.0 ) - 1.0;\r\n", + detailColor, detailMap, inDet)); + + texOp = new GenOp("tex2D(@, @.xy)", normalMap, inDet); + } + + // New terrain + // Get a var and accumulate the blend amount. Var *blendTotal = (Var*)LangElement::find( "blendTotal" ); if ( !blendTotal ) @@ -487,46 +594,6 @@ void TerrainDetailMapFeatGLSL::processPix( Vector &component } } - // If this is a prepass then we skip color. - if ( fd.features.hasFeature( MFT_PrePassConditioner ) ) - { - // Check to see if we have a gbuffer normal. - Var *gbNormal = (Var*)LangElement::find( "gbNormal" ); - - // If we have a gbuffer normal and we don't have a - // normal map feature then we need to lerp in a - // default normal else the normals below this layer - // will show thru. - if ( gbNormal && - !fd.features.hasFeature( MFT_TerrainNormalMap, detailIndex ) ) - { - Var *viewToTangent = getInViewToTangent( componentList ); - - meta->addStatement( new GenOp( " @ = lerp( @, tGetMatrix3Row(@, 2), min( @, @.w ) );\r\n", - gbNormal, gbNormal, viewToTangent, detailBlend, inDet ) ); - } - - output = meta; - return; - } - - Var *detailColor = (Var*)LangElement::find( "detailColor" ); - if ( !detailColor ) - { - detailColor = new Var; - detailColor->setType( "vec4" ); - detailColor->setName( "detailColor" ); - meta->addStatement( new GenOp( " @;\r\n", new DecOp( detailColor ) ) ); - } - - // Get the detail texture. - Var *detailMap = new Var; - detailMap->setType( "sampler2D" ); - detailMap->setName( String::ToString( "detailMap%d", detailIndex ) ); - detailMap->uniform = true; - detailMap->sampler = true; - detailMap->constNum = Var::getTexUnitNum(); // used as texture unit num here - // If we're using SM 3.0 then take advantage of // dynamic branching to skip layers per-pixel. @@ -557,9 +624,6 @@ void TerrainDetailMapFeatGLSL::processPix( Vector &component meta->addStatement( new GenOp( " @ *= @.y * @.w;\r\n", detailColor, detailInfo, inDet ) ); - Var *baseColor = (Var*)LangElement::find( "baseColor" ); - Var *outColor = (Var*)LangElement::find( "col" ); - meta->addStatement( new GenOp( " @ = lerp( @, @ + @, @ );\r\n", outColor, outColor, baseColor, detailColor, detailBlend ) ); @@ -585,9 +649,7 @@ ShaderFeature::Resources TerrainDetailMapFeatGLSL::getResources( const MaterialF res.numTexReg += 4; } - // If this isn't the prepass then we sample - // from the detail texture for diffuse coloring. - if ( !fd.features.hasFeature( MFT_PrePassConditioner ) ) + // sample from the detail texture for diffuse coloring. res.numTex += 1; // If we have parallax for this layer then we'll also @@ -602,6 +664,11 @@ ShaderFeature::Resources TerrainDetailMapFeatGLSL::getResources( const MaterialF return res; } +U32 TerrainDetailMapFeatGLSL::getOutputTargets( const MaterialFeatureData &fd ) const +{ + return fd.features[MFT_DeferredTerrainDetailMap] ? ShaderFeature::RenderTarget1 : ShaderFeature::DefaultTarget; +} + TerrainMacroMapFeatGLSL::TerrainMacroMapFeatGLSL() : mTorqueDep( "shaders/common/gl/torque.glsl" ), @@ -759,29 +826,6 @@ void TerrainMacroMapFeatGLSL::processPix( Vector &componentL // Add to the blend total. meta->addStatement( new GenOp( " @ = max( @, @ );\r\n", blendTotal, blendTotal, detailBlend ) ); - // If this is a prepass then we skip color. - if ( fd.features.hasFeature( MFT_PrePassConditioner ) ) - { - // Check to see if we have a gbuffer normal. - Var *gbNormal = (Var*)LangElement::find( "gbNormal" ); - - // If we have a gbuffer normal and we don't have a - // normal map feature then we need to lerp in a - // default normal else the normals below this layer - // will show thru. - if ( gbNormal && - !fd.features.hasFeature( MFT_TerrainNormalMap, detailIndex ) ) - { - Var *viewToTangent = getInViewToTangent( componentList ); - - meta->addStatement( new GenOp( " @ = lerp( @, tGetMatrix3Row(@, 2), min( @, @.w ) );\r\n", - gbNormal, gbNormal, viewToTangent, detailBlend, inDet ) ); - } - - output = meta; - return; - } - Var *detailColor = (Var*)LangElement::find( "macroColor" ); if ( !detailColor ) { @@ -826,8 +870,12 @@ void TerrainMacroMapFeatGLSL::processPix( Vector &componentL meta->addStatement( new GenOp( " @ *= @.y * @.w;\r\n", detailColor, detailInfo, inDet ) ); + ShaderFeature::OutputTarget target = ShaderFeature::DefaultTarget; - Var *outColor = (Var*)LangElement::find( "col" ); + if(fd.features.hasFeature(MFT_DeferredTerrainMacroMap)) + target= ShaderFeature::RenderTarget1; + + Var *outColor = (Var*)LangElement::find( getOutputTargetVarName(target) ); meta->addStatement( new GenOp( " @ = lerp( @, @ + @, @ );\r\n", outColor, outColor, outColor, detailColor, detailBlend ) ); @@ -850,9 +898,6 @@ ShaderFeature::Resources TerrainMacroMapFeatGLSL::getResources( const MaterialFe res.numTex += 1; } - // If this isn't the prepass then we sample - // from the detail texture for diffuse coloring. - if ( !fd.features.hasFeature( MFT_PrePassConditioner ) ) res.numTex += 1; // Finally we always send the detail texture @@ -862,6 +907,11 @@ ShaderFeature::Resources TerrainMacroMapFeatGLSL::getResources( const MaterialFe return res; } +U32 TerrainMacroMapFeatGLSL::getOutputTargets( const MaterialFeatureData &fd ) const +{ + return fd.features[MFT_DeferredTerrainMacroMap] ? ShaderFeature::RenderTarget1 : ShaderFeature::DefaultTarget; +} + void TerrainNormalMapFeatGLSL::processVert( Vector &componentList, const MaterialFeatureData &fd ) { @@ -881,9 +931,6 @@ void TerrainNormalMapFeatGLSL::processVert( Vector &component void TerrainNormalMapFeatGLSL::processPix( Vector &componentList, const MaterialFeatureData &fd ) { - // We only need to process normals during the prepass. - if ( !fd.features.hasFeature( MFT_PrePassConditioner ) ) - return; MultiLine *meta = new MultiLine; @@ -1023,7 +1070,12 @@ ShaderFeature::Resources TerrainLightMapFeatGLSL::getResources( const MaterialFe void TerrainAdditiveFeatGLSL::processPix( Vector &componentList, const MaterialFeatureData &fd ) { - Var *color = (Var*) LangElement::find( "col" ); + Var *color = NULL; + if (fd.features[MFT_DeferredTerrainDetailMap]) + color = (Var*) LangElement::find( getOutputTargetVarName(ShaderFeature::RenderTarget1) ); + else + color = (Var*) LangElement::find( getOutputTargetVarName(ShaderFeature::DefaultTarget) ); + Var *blendTotal = (Var*)LangElement::find( "blendTotal" ); if ( !color || !blendTotal ) return; @@ -1035,3 +1087,40 @@ void TerrainAdditiveFeatGLSL::processPix( Vector &componentLis output = meta; } + +//standard matInfo map contains data of the form .r = bitflags, .g = (will contain AO), +//.b = specular strength, a= spec power. +//here, it's merely a cutout for now, so that lightmapping (target3) doesn't get mangled. +//we'll most likely revisit that later. possibly several ways... + +U32 TerrainBlankInfoMapFeatGLSL::getOutputTargets(const MaterialFeatureData &fd) const +{ + return fd.features[MFT_isDeferred] ? ShaderFeature::RenderTarget2 : ShaderFeature::RenderTarget1; +} + +void TerrainBlankInfoMapFeatGLSL::processPix(Vector &componentList, + const MaterialFeatureData &fd) +{ + // search for material var + Var *material; + OutputTarget targ = RenderTarget1; + if (fd.features[MFT_isDeferred]) + { + targ = RenderTarget2; + } + material = (Var*)LangElement::find(getOutputTargetVarName(targ)); + + MultiLine * meta = new MultiLine; + if (!material) + { + // create color var + material = new Var; + material->setType("vec4"); + material->setName(getOutputTargetVarName(targ)); + material->setStructName("OUT"); + } + + meta->addStatement(new GenOp(" @ = float4(0.0,0.0,0.0,0.0001);\r\n", material)); + + output = meta; +} diff --git a/Engine/source/terrain/glsl/terrFeatureGLSL.h b/Engine/source/terrain/glsl/terrFeatureGLSL.h index 6cb3a3c0c..7839558b1 100644 --- a/Engine/source/terrain/glsl/terrFeatureGLSL.h +++ b/Engine/source/terrain/glsl/terrFeatureGLSL.h @@ -67,6 +67,8 @@ public: virtual Resources getResources( const MaterialFeatureData &fd ); virtual String getName() { return "Terrain Base Texture"; } + + virtual U32 getOutputTargets( const MaterialFeatureData &fd ) const; }; @@ -90,6 +92,8 @@ public: virtual Resources getResources( const MaterialFeatureData &fd ); virtual String getName() { return "Terrain Detail Texture"; } + + virtual U32 getOutputTargets( const MaterialFeatureData &fd ) const; }; @@ -113,6 +117,8 @@ public: virtual Resources getResources( const MaterialFeatureData &fd ); virtual String getName() { return "Terrain Macro Texture"; } + + virtual U32 getOutputTargets( const MaterialFeatureData &fd ) const; }; @@ -154,4 +160,15 @@ public: virtual String getName() { return "Terrain Additive"; } }; +class TerrainBlankInfoMapFeatGLSL : public ShaderFeatureGLSL +{ +public: + + virtual void processPix(Vector &componentList, + const MaterialFeatureData &fd); + + virtual U32 getOutputTargets(const MaterialFeatureData &fd) const; + virtual String getName() { return "Blank Matinfo map"; } +}; + #endif // _TERRFEATUREGLSL_H_ diff --git a/Engine/source/terrain/hlsl/terrFeatureHLSL.cpp b/Engine/source/terrain/hlsl/terrFeatureHLSL.cpp index 6ef1c2009..c9ae13414 100644 --- a/Engine/source/terrain/hlsl/terrFeatureHLSL.cpp +++ b/Engine/source/terrain/hlsl/terrFeatureHLSL.cpp @@ -26,6 +26,7 @@ #include "terrain/terrFeatureTypes.h" #include "materials/materialFeatureTypes.h" #include "materials/materialFeatureData.h" +#include "materials/processedMaterial.h" #include "gfx/gfxDevice.h" #include "shaderGen/langElement.h" #include "shaderGen/shaderOp.h" @@ -47,9 +48,12 @@ namespace FEATUREMGR->registerFeature( MFT_TerrainMacroMap, new TerrainMacroMapFeatHLSL ); FEATUREMGR->registerFeature( MFT_TerrainLightMap, new TerrainLightMapFeatHLSL ); FEATUREMGR->registerFeature( MFT_TerrainSideProject, new NamedFeatureHLSL( "Terrain Side Projection" ) ); - FEATUREMGR->registerFeature( MFT_TerrainAdditive, new TerrainAdditiveFeatHLSL ); + FEATUREMGR->registerFeature( MFT_TerrainAdditive, new TerrainAdditiveFeatHLSL ); + FEATUREMGR->registerFeature( MFT_DeferredTerrainBaseMap, new TerrainBaseMapFeatHLSL ); + FEATUREMGR->registerFeature( MFT_DeferredTerrainMacroMap, new TerrainMacroMapFeatHLSL ); + FEATUREMGR->registerFeature( MFT_DeferredTerrainDetailMap, new TerrainDetailMapFeatHLSL ); + FEATUREMGR->registerFeature( MFT_DeferredTerrainBlankInfoMap, new TerrainBlankInfoMapFeatHLSL ); } - }; MODULE_BEGIN( TerrainFeatHLSL ) @@ -250,10 +254,6 @@ void TerrainBaseMapFeatHLSL::processPix( Vector &componentLis // grab connector texcoord register Var *texCoord = getInTexCoord( "texCoord", "float3", true, componentList ); - // We do nothing more if this is a prepass. - if ( fd.features.hasFeature( MFT_PrePassConditioner ) ) - return; - // create texture var Var *diffuseMap = new Var; diffuseMap->setType( "sampler2D" ); @@ -269,8 +269,15 @@ void TerrainBaseMapFeatHLSL::processPix( Vector &componentLis baseColor->setName( "baseColor" ); meta->addStatement( new GenOp( " @ = tex2D( @, @.xy );\r\n", new DecOp( baseColor ), diffuseMap, texCoord ) ); meta->addStatement(new GenOp(" @ = toLinear(@);\r\n", baseColor, baseColor)); - meta->addStatement( new GenOp( " @;\r\n", assignColor( baseColor, Material::Mul ) ) ); + ShaderFeature::OutputTarget target = ShaderFeature::DefaultTarget; + + if (fd.features.hasFeature(MFT_isDeferred)) + { + target= ShaderFeature::RenderTarget1; + } + + meta->addStatement( new GenOp( " @;\r\n", assignColor( baseColor, Material::Mul,NULL,target ) ) ); output = meta; } @@ -278,14 +285,16 @@ ShaderFeature::Resources TerrainBaseMapFeatHLSL::getResources( const MaterialFea { Resources res; res.numTexReg = 1; - - // We only sample from the base map during a diffuse pass. - if ( !fd.features.hasFeature( MFT_PrePassConditioner ) ) res.numTex = 1; return res; } +U32 TerrainBaseMapFeatHLSL::getOutputTargets( const MaterialFeatureData &fd ) const +{ + return fd.features[MFT_DeferredTerrainBaseMap] ? ShaderFeature::RenderTarget1 : ShaderFeature::DefaultTarget; +} + TerrainDetailMapFeatHLSL::TerrainDetailMapFeatHLSL() : mTorqueDep( "shaders/common/torque.hlsl" ), mTerrainDep( "shaders/common/terrain/terrain.hlsl" ) @@ -477,7 +486,7 @@ void TerrainDetailMapFeatHLSL::processPix( Vector &component // Call the library function to do the rest. if(fd.features.hasFeature( MFT_IsDXTnm, detailIndex ) ) { - meta->addStatement( new GenOp( " @.xy += parallaxOffsetDxtnm( @, @.xy, @, @.z * @ );\r\n", + meta->addStatement( new GenOp( " @.xy += parallaxOffsetDxtnm( @, @.xy, @, @.z * @ );\r\n", inDet, normalMap, inDet, negViewTS, detailInfo, detailBlend ) ); } else @@ -487,29 +496,6 @@ void TerrainDetailMapFeatHLSL::processPix( Vector &component } } - // If this is a prepass then we skip color. - if ( fd.features.hasFeature( MFT_PrePassConditioner ) ) - { - // Check to see if we have a gbuffer normal. - Var *gbNormal = (Var*)LangElement::find( "gbNormal" ); - - // If we have a gbuffer normal and we don't have a - // normal map feature then we need to lerp in a - // default normal else the normals below this layer - // will show thru. - if ( gbNormal && - !fd.features.hasFeature( MFT_TerrainNormalMap, detailIndex ) ) - { - Var *viewToTangent = getInViewToTangent( componentList ); - - meta->addStatement( new GenOp( " @ = lerp( @, @[2], min( @, @.w ) );\r\n", - gbNormal, gbNormal, viewToTangent, detailBlend, inDet ) ); - } - - output = meta; - return; - } - Var *detailColor = (Var*)LangElement::find( "detailColor" ); if ( !detailColor ) { @@ -543,21 +529,35 @@ void TerrainDetailMapFeatHLSL::processPix( Vector &component // We take two color samples and lerp between them for // side projection layers... else a single sample. // + + //Sampled detail texture that is not expanded + Var *detailTex = new Var; + detailTex->setType("float4"); + detailTex->setName("detailTex"); + meta->addStatement( new GenOp( " @;\r\n", new DecOp( detailTex ) ) ); + if ( fd.features.hasFeature( MFT_TerrainSideProject, detailIndex ) ) { - meta->addStatement( new GenOp( " @ = ( lerp( tex2D( @, @.yz ), tex2D( @, @.xz ), @.z ) * 2.0 ) - 1.0;\r\n", - detailColor, detailMap, inDet, detailMap, inDet, inTex ) ); + meta->addStatement( new GenOp(" @ = lerp( tex2D( @, @.yz ), tex2D( @, @.xz ), @.z);\r\n",detailTex,detailMap,inDet,detailMap,inDet,inTex)); + meta->addStatement( new GenOp( " @ = ( @ * 2.0 ) - 1.0;\r\n", detailColor, detailTex) ); } else { - meta->addStatement( new GenOp( " @ = ( tex2D( @, @.xy ) * 2.0 ) - 1.0;\r\n", - detailColor, detailMap, inDet ) ); + meta->addStatement( new GenOp(" @ = tex2D(@,@.xy);\r\n",detailTex,detailMap,inDet)); + meta->addStatement( new GenOp( " @ = ( @ * 2.0 ) - 1.0;\r\n", + detailColor, detailTex) ); } meta->addStatement( new GenOp( " @ *= @.y * @.w;\r\n", detailColor, detailInfo, inDet ) ); - Var *outColor = (Var*)LangElement::find( "col" ); + Var *baseColor = (Var*)LangElement::find( "baseColor" ); + ShaderFeature::OutputTarget target = ShaderFeature::DefaultTarget; + + if(fd.features.hasFeature( MFT_DeferredTerrainDetailMap )) + target= ShaderFeature::RenderTarget1; + + Var *outColor = (Var*)LangElement::find( getOutputTargetVarName(target) ); meta->addStatement( new GenOp( " @ += @ * @;\r\n", outColor, detailColor, detailBlend)); @@ -584,9 +584,7 @@ ShaderFeature::Resources TerrainDetailMapFeatHLSL::getResources( const MaterialF res.numTexReg += 4; } - // If this isn't the prepass then we sample - // from the detail texture for diffuse coloring. - if ( !fd.features.hasFeature( MFT_PrePassConditioner ) ) + // sample from the detail texture for diffuse coloring. res.numTex += 1; // If we have parallax for this layer then we'll also @@ -601,6 +599,11 @@ ShaderFeature::Resources TerrainDetailMapFeatHLSL::getResources( const MaterialF return res; } +U32 TerrainDetailMapFeatHLSL::getOutputTargets( const MaterialFeatureData &fd ) const +{ + return fd.features[MFT_DeferredTerrainDetailMap] ? ShaderFeature::RenderTarget1 : ShaderFeature::DefaultTarget; +} + TerrainMacroMapFeatHLSL::TerrainMacroMapFeatHLSL() : mTorqueDep( "shaders/common/torque.hlsl" ), @@ -758,29 +761,6 @@ void TerrainMacroMapFeatHLSL::processPix( Vector &componentL // Add to the blend total. meta->addStatement( new GenOp( " @ += @;\r\n", blendTotal, detailBlend ) ); - // If this is a prepass then we skip color. - if ( fd.features.hasFeature( MFT_PrePassConditioner ) ) - { - // Check to see if we have a gbuffer normal. - Var *gbNormal = (Var*)LangElement::find( "gbNormal" ); - - // If we have a gbuffer normal and we don't have a - // normal map feature then we need to lerp in a - // default normal else the normals below this layer - // will show thru. - if ( gbNormal && - !fd.features.hasFeature( MFT_TerrainNormalMap, detailIndex ) ) - { - Var *viewToTangent = getInViewToTangent( componentList ); - - meta->addStatement( new GenOp( " @ = lerp( @, @[2], min( @, @.w ) );\r\n", - gbNormal, gbNormal, viewToTangent, detailBlend, inDet ) ); - } - - output = meta; - return; - } - Var *detailColor = (Var*)LangElement::find( "macroColor" ); if ( !detailColor ) { @@ -826,8 +806,14 @@ void TerrainMacroMapFeatHLSL::processPix( Vector &componentL meta->addStatement( new GenOp( " @ *= @.y * @.w;\r\n", detailColor, detailInfo, inDet ) ); - //Var *baseColor = (Var*)LangElement::find( "baseColor" ); - Var *outColor = (Var*)LangElement::find( "col" ); + Var *baseColor = (Var*)LangElement::find( "baseColor" ); + + ShaderFeature::OutputTarget target = ShaderFeature::DefaultTarget; + + if(fd.features.hasFeature(MFT_DeferredTerrainMacroMap)) + target= ShaderFeature::RenderTarget1; + + Var *outColor = (Var*)LangElement::find( getOutputTargetVarName(target) ); meta->addStatement(new GenOp(" @ += @ * @;\r\n", outColor, detailColor, detailBlend)); @@ -837,8 +823,6 @@ void TerrainMacroMapFeatHLSL::processPix( Vector &componentL output = meta; } - - ShaderFeature::Resources TerrainMacroMapFeatHLSL::getResources( const MaterialFeatureData &fd ) { Resources res; @@ -850,9 +834,6 @@ ShaderFeature::Resources TerrainMacroMapFeatHLSL::getResources( const MaterialFe res.numTex += 1; } - // If this isn't the prepass then we sample - // from the detail texture for diffuse coloring. - if ( !fd.features.hasFeature( MFT_PrePassConditioner ) ) res.numTex += 1; // Finally we always send the detail texture @@ -862,6 +843,11 @@ ShaderFeature::Resources TerrainMacroMapFeatHLSL::getResources( const MaterialFe return res; } +U32 TerrainMacroMapFeatHLSL::getOutputTargets( const MaterialFeatureData &fd ) const +{ + return fd.features[MFT_DeferredTerrainMacroMap] ? ShaderFeature::RenderTarget1 : ShaderFeature::DefaultTarget; +} + void TerrainNormalMapFeatHLSL::processVert( Vector &componentList, const MaterialFeatureData &fd ) { @@ -881,9 +867,6 @@ void TerrainNormalMapFeatHLSL::processVert( Vector &component void TerrainNormalMapFeatHLSL::processPix( Vector &componentList, const MaterialFeatureData &fd ) { - // We only need to process normals during the prepass. - if ( !fd.features.hasFeature( MFT_PrePassConditioner ) ) - return; MultiLine *meta = new MultiLine; @@ -1019,11 +1002,15 @@ ShaderFeature::Resources TerrainLightMapFeatHLSL::getResources( const MaterialFe return res; } - void TerrainAdditiveFeatHLSL::processPix( Vector &componentList, const MaterialFeatureData &fd ) { - Var *color = (Var*) LangElement::find( "col" ); + Var *color = NULL; + if (fd.features[MFT_DeferredTerrainDetailMap]) + color = (Var*) LangElement::find( getOutputTargetVarName(ShaderFeature::RenderTarget1) ); + else + color = (Var*) LangElement::find( getOutputTargetVarName(ShaderFeature::DefaultTarget) ); + Var *blendTotal = (Var*)LangElement::find( "blendTotal" ); if ( !color || !blendTotal ) return; @@ -1033,5 +1020,43 @@ void TerrainAdditiveFeatHLSL::processPix( Vector &componentLis meta->addStatement( new GenOp( " clip( @ - 0.0001 );\r\n", blendTotal ) ); meta->addStatement( new GenOp( " @.a = @;\r\n", color, blendTotal ) ); + + output = meta; +} + +//standard matInfo map contains data of the form .r = bitflags, .g = (will contain AO), +//.b = specular strength, a= spec power. +//here, it's merely a cutout for now, so that lightmapping (target3) doesn't get mangled. +//we'll most likely revisit that later. possibly several ways... + +U32 TerrainBlankInfoMapFeatHLSL::getOutputTargets(const MaterialFeatureData &fd) const +{ + return fd.features[MFT_DeferredTerrainBaseMap] ? ShaderFeature::RenderTarget2 : ShaderFeature::RenderTarget1; +} + +void TerrainBlankInfoMapFeatHLSL::processPix(Vector &componentList, + const MaterialFeatureData &fd) +{ + // search for material var + Var *material; + OutputTarget targ = RenderTarget1; + if (fd.features[MFT_isDeferred]) + { + targ = RenderTarget2; + } + material = (Var*)LangElement::find(getOutputTargetVarName(targ)); + + MultiLine * meta = new MultiLine; + if (!material) + { + // create color var + material = new Var; + material->setType("fragout"); + material->setName(getOutputTargetVarName(targ)); + material->setStructName("OUT"); + } + + meta->addStatement(new GenOp(" @ = float4(0.0,0.0,0.0,0.0001);\r\n", material)); + output = meta; } diff --git a/Engine/source/terrain/hlsl/terrFeatureHLSL.h b/Engine/source/terrain/hlsl/terrFeatureHLSL.h index e6297f3b4..2effbdc36 100644 --- a/Engine/source/terrain/hlsl/terrFeatureHLSL.h +++ b/Engine/source/terrain/hlsl/terrFeatureHLSL.h @@ -68,6 +68,8 @@ public: virtual Resources getResources( const MaterialFeatureData &fd ); virtual String getName() { return "Terrain Base Texture"; } + + virtual U32 getOutputTargets( const MaterialFeatureData &fd ) const; }; @@ -91,6 +93,8 @@ public: virtual Resources getResources( const MaterialFeatureData &fd ); virtual String getName() { return "Terrain Detail Texture"; } + + virtual U32 getOutputTargets( const MaterialFeatureData &fd ) const; }; @@ -114,6 +118,8 @@ public: virtual Resources getResources( const MaterialFeatureData &fd ); virtual String getName() { return "Terrain Macro Texture"; } + + virtual U32 getOutputTargets( const MaterialFeatureData &fd ) const; }; @@ -155,4 +161,15 @@ public: virtual String getName() { return "Terrain Additive"; } }; +class TerrainBlankInfoMapFeatHLSL : public TerrainFeatHLSL +{ +public: + + virtual void processPix(Vector &componentList, + const MaterialFeatureData &fd); + + virtual U32 getOutputTargets(const MaterialFeatureData &fd) const; + virtual String getName() { return "Blank Matinfo map"; } +}; + #endif // _TERRFEATUREHLSL_H_ diff --git a/Engine/source/terrain/terrCellMaterial.cpp b/Engine/source/terrain/terrCellMaterial.cpp index 101c21f0c..3b96a1318 100644 --- a/Engine/source/terrain/terrCellMaterial.cpp +++ b/Engine/source/terrain/terrCellMaterial.cpp @@ -37,6 +37,7 @@ #include "gfx/util/screenspace.h" #include "lighting/advanced/advancedLightBinManager.h" +S32 sgMaxTerrainMaterialsPerPass = 3; AFTER_MODULE_INIT( MaterialManager ) { @@ -310,12 +311,12 @@ bool TerrainCellMaterial::_createPass( Vector *materials, if ( GFX->getPixelShaderVersion() < 3.0f ) baseOnly = true; - // NOTE: At maximum we only try to combine 3 materials + // NOTE: At maximum we only try to combine sgMaxTerrainMaterialsPerPass materials // into a single pass. This is sub-optimal for the simplest // cases, but the most common case results in much fewer // shader generation failures and permutations leading to // faster load time and less hiccups during gameplay. - U32 matCount = getMin( 3, materials->size() ); + U32 matCount = getMin( sgMaxTerrainMaterialsPerPass, materials->size() ); Vector normalMaps; @@ -349,24 +350,27 @@ bool TerrainCellMaterial::_createPass( Vector *materials, { FeatureSet features; features.addFeature( MFT_VertTransform ); - features.addFeature( MFT_TerrainBaseMap ); if ( prePassMat ) { features.addFeature( MFT_EyeSpaceDepthOut ); features.addFeature( MFT_PrePassConditioner ); + features.addFeature( MFT_DeferredTerrainBaseMap ); + features.addFeature(MFT_isDeferred); if ( advancedLightmapSupport ) - features.addFeature( MFT_RenderTarget1_Zero ); + features.addFeature( MFT_RenderTarget3_Zero ); } else { + features.addFeature( MFT_TerrainBaseMap ); features.addFeature( MFT_RTLighting ); // The HDR feature is always added... it will compile out // if HDR is not enabled in the engine. features.addFeature( MFT_HDROut ); } + features.addFeature(MFT_DeferredTerrainBlankInfoMap); // Enable lightmaps and fogging if we're in BL. if ( reflectMat || useBLM ) @@ -405,8 +409,16 @@ bool TerrainCellMaterial::_createPass( Vector *materials, // check for macro detail texture if ( !(mat->getMacroSize() <= 0 || mat->getMacroDistance() <= 0 || mat->getMacroMap().isEmpty() ) ) + { + if(prePassMat) + features.addFeature( MFT_DeferredTerrainMacroMap, featureIndex ); + else features.addFeature( MFT_TerrainMacroMap, featureIndex ); + } + if(prePassMat) + features.addFeature( MFT_DeferredTerrainDetailMap, featureIndex ); + else features.addFeature( MFT_TerrainDetailMap, featureIndex ); pass->materials.push_back( (*materials)[i] ); @@ -536,8 +548,8 @@ bool TerrainCellMaterial::_createPass( Vector *materials, // MFT_TerrainAdditive feature to lerp the // output normal with the previous pass. // - if ( prePassMat ) - desc.setColorWrites( true, true, false, false ); + //if ( prePassMat ) + //desc.setColorWrites( true, true, false, false ); } // We write to the zbuffer if this is a prepass @@ -656,9 +668,9 @@ bool TerrainCellMaterial::_createPass( Vector *materials, if ( prePassMat ) desc.addDesc( RenderPrePassMgr::getOpaqueStenciWriteDesc( false ) ); - // Flip the cull for reflection materials. - if ( reflectMat ) - desc.setCullMode( GFXCullCW ); + // Shut off culling for prepass materials (reflection support). + if ( prePassMat ) + desc.setCullMode( GFXCullNone ); pass->stateBlock = GFX->createStateBlock( desc ); diff --git a/Engine/source/terrain/terrFeatureTypes.cpp b/Engine/source/terrain/terrFeatureTypes.cpp index b65085916..5eb8ff0e0 100644 --- a/Engine/source/terrain/terrFeatureTypes.cpp +++ b/Engine/source/terrain/terrFeatureTypes.cpp @@ -34,4 +34,8 @@ ImplementFeatureType( MFT_TerrainMacroMap, MFG_Texture, 104.0f, false ); ImplementFeatureType( MFT_TerrainLightMap, MFG_Texture, 105.0f, false ); ImplementFeatureType( MFT_TerrainSideProject, MFG_Texture, 106.0f, false ); ImplementFeatureType( MFT_TerrainAdditive, MFG_PostProcess, 999.0f, false ); - +//Deferred Shading +ImplementFeatureType( MFT_DeferredTerrainBaseMap, MFG_Texture, 100.1f, false ); +ImplementFeatureType( MFT_DeferredTerrainDetailMap, MFG_Texture, 102.1f, false ); +ImplementFeatureType( MFT_DeferredTerrainMacroMap, MFG_Texture, 104.1f, false ); +ImplementFeatureType( MFT_DeferredTerrainBlankInfoMap, MFG_Texture, 104.1f, false); diff --git a/Engine/source/terrain/terrFeatureTypes.h b/Engine/source/terrain/terrFeatureTypes.h index 0d2826da9..6246c5fa6 100644 --- a/Engine/source/terrain/terrFeatureTypes.h +++ b/Engine/source/terrain/terrFeatureTypes.h @@ -35,6 +35,12 @@ DeclareFeatureType( MFT_TerrainParallaxMap ); DeclareFeatureType( MFT_TerrainLightMap ); DeclareFeatureType( MFT_TerrainSideProject ); DeclareFeatureType( MFT_TerrainAdditive ); +//Deferred Shading +DeclareFeatureType( MFT_DeferredTerrainBaseMap ); +DeclareFeatureType( MFT_DeferredTerrainDetailMap ); +DeclareFeatureType( MFT_DeferredTerrainMacroMap ); +DeclareFeatureType( MFT_DeferredTerrainBlankInfoMap ); + #endif // _TERRFEATURETYPES_H_ diff --git a/Templates/Full/game/shaders/common/terrain/terrain.glsl b/Templates/Full/game/shaders/common/terrain/terrain.glsl index 79f80888c..756edd553 100644 --- a/Templates/Full/game/shaders/common/terrain/terrain.glsl +++ b/Templates/Full/game/shaders/common/terrain/terrain.glsl @@ -32,6 +32,7 @@ float calcBlend( float texId, vec2 layerCoord, float layerSize, vec4 layerSample vec4 diff = clamp( abs( layerSample - texId ), 0.0, 1.0 ); float noBlend = float(any( bvec4(1 - diff) )); + // Check if any of the layer samples // match the current texture id. vec4 factors = vec4(0); for(int i = 0; i < 4; i++) diff --git a/Templates/Full/game/tools/materialEditor/gui/guiMaterialPropertiesWindow.ed.gui b/Templates/Full/game/tools/materialEditor/gui/guiMaterialPropertiesWindow.ed.gui index 2062f43f3..63ce28e2a 100644 --- a/Templates/Full/game/tools/materialEditor/gui/guiMaterialPropertiesWindow.ed.gui +++ b/Templates/Full/game/tools/materialEditor/gui/guiMaterialPropertiesWindow.ed.gui @@ -1354,132 +1354,6 @@ bitmap = "tools/gui/images/delete"; }; }; - new GuiBitmapCtrl(){ - position="6 357"; - extent ="175 2"; - HorizSizing = "width"; - bitmap ="tools/gui/images/separator-v"; - }; - new GuiContainer(){ // Environment Map - profile="ToolsGuiDefaultProfile"; - isContainer = "1"; - position = "6 359"; - Extent = "185 52"; - HorizSizing = "width"; - - new GuiBitmapCtrl() { - canSaveDynamicFields = "0"; - internalName = "envMapDisplayBitmap"; - Enabled = "1"; - isContainer = "0"; - Profile = "ToolsGuiDefaultProfile"; - HorizSizing = "right"; - VertSizing = "bottom"; - position = "1 1"; - Extent = "48 48"; - MinExtent = "8 2"; - canSave = "1"; - Visible = "1"; - hovertime = "1000"; - bitmap = "tools/materialeditor/gui/unknownImage"; - wrap = "0"; - }; - new GuiTextCtrl() { - canSaveDynamicFields = "0"; - Enabled = "1"; - isContainer = "0"; - Profile = "EditorTextProfile"; - HorizSizing = "right"; - VertSizing = "bottom"; - position = "56 -3"; - Extent = "72 18"; - MinExtent = "8 2"; - canSave = "1"; - Visible = "1"; - hovertime = "1000"; - Margin = "0 0 0 0"; - Padding = "0 0 0 0"; - AnchorTop = "1"; - AnchorBottom = "0"; - AnchorLeft = "1"; - AnchorRight = "0"; - text = "Env Map"; - maxLength = "1024"; - }; - new GuiBitmapButtonCtrl() { - canSaveDynamicFields = "0"; - Enabled = "1"; - isContainer = "0"; - Profile = "ToolsGuiDefaultProfile"; - HorizSizing = "right"; - VertSizing = "bottom"; - position = "1 1"; - Extent = "48 48"; - MinExtent = "8 2"; - canSave = "1"; - Visible = "1"; - Command = "MaterialEditorGui.updateTextureMap(\"env\", 1);"; - tooltipprofile = "ToolsGuiDefaultProfile"; - ToolTip = "Change the active Environment Map for this layer."; - hovertime = "1000"; - groupNum = "-1"; - buttonType = "PushButton"; - useMouseEvents = "0"; - bitmap = "tools/materialEditor/gui/cubemapBtnBorder"; - }; - new GuiTextCtrl() { - canSaveDynamicFields = "0"; - internalName = "envMapNameText"; - Enabled = "1"; - isContainer = "0"; - Profile = "ToolsGuiTextProfile"; - HorizSizing = "width"; - VertSizing = "bottom"; - position = "56 16"; - Extent = "143 17"; - MinExtent = "8 2"; - canSave = "1"; - Visible = "1"; - hovertime = "1000"; - Margin = "0 0 0 0"; - Padding = "0 0 0 0"; - AnchorTop = "1"; - AnchorBottom = "0"; - AnchorLeft = "1"; - AnchorRight = "0"; - text = "None"; - maxLength = "1024"; - }; - new GuiButtonCtrl(){ - profile="ToolsGuiButtonProfile"; - text ="Edit"; - HorizSizing = "left"; - VertSizing = "bottom"; - position = "134 34"; - Extent = "40 16"; - buttonType = "PushButton"; - command="MaterialEditorGui.updateTextureMap(\"env\", 1);"; - }; - new GuiBitmapButtonCtrl() { - canSaveDynamicFields = "0"; - Enabled = "1"; - isContainer = "0"; - Profile = "ToolsGuiDefaultProfile"; - HorizSizing = "left"; - VertSizing = "bottom"; - position = "177 34"; - Extent = "16 16"; - MinExtent = "8 2"; - canSave = "1"; - Visible = "1"; - Command = "MaterialEditorGui.updateTextureMap(\"env\", 0);"; - hovertime = "1000"; - groupNum = "-1"; - buttonType = "PushButton"; - useMouseEvents = "0"; - bitmap = "tools/gui/images/delete"; - }; - }; }; }; new GuiRolloutCtrl() { @@ -1491,6 +1365,7 @@ Position = "0 0"; Extent = "195 0"; Caption = "Accumulation Properties"; + Expanded = false; Margin = "-1 0 0 0"; DragSizable = false; container = true; @@ -1967,6 +1842,7 @@ Position = "0 0"; Extent = "185 0"; Caption = "Lighting Properties"; + Expanded = false; Margin = "-1 0 0 0"; DragSizable = false; container = true; @@ -2364,101 +2240,6 @@ useMouseEvents = "0"; useInactiveState = "0"; }; - new GuiCheckBoxCtrl() { - canSaveDynamicFields = "0"; - internalName = "subSurfaceCheckbox"; - Enabled = "1"; - isContainer = "0"; - Profile = "ToolsGuiCheckBoxProfile"; - HorizSizing = "right"; - VertSizing = "bottom"; - position = "8 46"; - Extent = "79 16"; - MinExtent = "8 2"; - canSave = "1"; - Visible = "1"; - Command = "MaterialEditorGui.updateActiveMaterial(\"subSurface[\" @ MaterialEditorGui.currentLayer @ \"]\", $ThisControl.getValue());"; - tooltipprofile = "ToolsGuiDefaultProfile"; - ToolTip = "Enables the use of subsurface scattering for this layer."; - hovertime = "1000"; - text = "Sub Surface"; - groupNum = "-1"; - buttonType = "ToggleButton"; - useMouseEvents = "0"; - useInactiveState = "0"; - }; - new GuiSwatchButtonCtrl() { - canSaveDynamicFields = "0"; - internalName = "subSurfaceColorSwatch"; - Enabled = "1"; - isContainer = "0"; - Profile = "GuiInspectorSwatchButtonProfile"; - HorizSizing = "right"; - VertSizing = "bottom"; - position = "90 46"; - Extent = "16 16"; - MinExtent = "8 2"; - canSave = "1"; - Visible = "1"; - Command = "getColorF(materialEd_PreviewMaterial.subSurfaceColor[MaterialEditorGui.currentLayer], \"MaterialEditorGui.updateSubSurfaceColor\");"; - tooltip = "Set the subsurface scattering color"; - hovertime = "1000"; - groupNum = "-1"; - buttonType = "PushButton"; - useMouseEvents = "0"; - }; - new GuiTextEditCtrl() { - canSaveDynamicFields = "0"; - internalName = "subSurfaceRolloffTextEdit"; - Enabled = "1"; - isContainer = "0"; - Profile = "ToolsGuiTextEditProfile"; - HorizSizing = "right"; - VertSizing = "bottom"; - position = "114 45"; - Extent = "29 18"; - MinExtent = "8 2"; - canSave = "1"; - Visible = "1"; - tooltip = "Set the subsurface rolloff factor"; - Command = "MaterialEditorGui.updateActiveMaterial(\"subSurfaceRolloff[\" @ MaterialEditorGui.currentLayer @ \"]\", $ThisControl.getText());"; - hovertime = "1000"; - AnchorTop = "1"; - AnchorBottom = "0"; - AnchorLeft = "1"; - AnchorRight = "0"; - text = "32"; - maxLength = "5"; - }; - new GuiTextCtrl() { - HorizSizing = "right"; - VertSizing = "bottom"; - position = "9 65"; - Extent = "89 16"; - text = "Minnaert constant"; - }; - new GuiTextEditCtrl() { - canSaveDynamicFields = "0"; - internalName = "minnaertTextEdit"; - Enabled = "1"; - isContainer = "0"; - Profile = "ToolsGuiTextEditProfile"; - HorizSizing = "right"; - VertSizing = "bottom"; - position = "114 65"; - Extent = "29 18"; - MinExtent = "8 2"; - canSave = "1"; - Visible = "1"; - Command = "MaterialEditorGui.updateActiveMaterial(\"minnaertConstant[\" @ MaterialEditorGui.currentLayer @ \"]\", $ThisControl.getText());"; - hovertime = "1000"; - AnchorTop = "1"; - AnchorBottom = "0"; - AnchorLeft = "1"; - AnchorRight = "0"; - text = "32"; - maxLength = "3"; - }; }; }; }; @@ -2471,9 +2252,9 @@ Position = "0 0"; Extent = "185 0"; Caption = "Animation Properties"; + Expanded = false; Margin = "-1 0 0 0"; DragSizable = false; - Expanded = false; container = true; parentRollout = %this.rollout; object = %behavior; @@ -3283,6 +3064,7 @@ Position = "0 0"; Extent = "202 0"; Caption = "Advanced (all layers)"; + Expanded = false; Margin = "4 4 4 0"; DragSizable = false; container = true; @@ -3519,7 +3301,7 @@ Profile = "ToolsGuiCheckBoxProfile"; HorizSizing = "right"; VertSizing = "bottom"; - position = "100 56"; + position = "105 55"; Extent = "85 16"; MinExtent = "8 2"; canSave = "1"; From 5ed06fff9d1bb7fd5efe4648375ef50962456adf Mon Sep 17 00:00:00 2001 From: Azaezel Date: Tue, 16 Feb 2016 02:29:54 -0600 Subject: [PATCH 079/109] Script: by and large, Opengl branch compatibility alterations, though do again note the inclusion of sampler["lightBuffer"] = "#lightinfo"; sampler["colorBuffer"] = "#color"; sampler["matInfoBuffer"] = "#matinfo"; and samplerNames[5] = "$lightBuffer"; samplerNames[6] = "$colorBuffer"; samplerNames[7] = "$matInfoBuffer"; entries. This is where the engine knows to pass along a given rendertarget for input into a predefined shader, as opposed to the prior phase's output to targets within procedural ones. Shader: the XXXLight.hlsl/glsls account for alterations in inputs, check for emissive and translucency, apply Felix's Normal Mapped Ambient. and pass the results along to AL_DeferredOutput for final computation before returning the result. the lighting.hlsl/.glsl consissts of removal of the overridden engine-specific phong specular variant, and defines the AL_DeferredOutput method, which equates to the previously used pixspecular feature defined along the lines of http://books.google.com/books?id=GY-AAwAAQBAJ&pg=PA112&lpg=PA112&dq=blinn+phong+specular+gloss+hlsl&source=bl&ots=q9SKJkmWHB&sig=uLIHX10Zul0X0LL2ehSMq7IFBIM&hl=en&sa=X&ei=DbcsVPeWEdW1yASDy4LYDw&ved=0CB4Q6AEwAA#v=onepage&q=gloss%20&f=false also includes visualizers Long term impact: This area, along with the \game\shaders\common\lighting\advanced\lightingUtils.hlsl/.glsl pair will be where we plug in properly attenuated Cook-Torrence later, presuming the impact is not to hefty. --- .../client/lighting/advanced/shaders.cs | 26 ++++++++++- .../Full/game/shaders/common/gl/lighting.glsl | 43 ++++++++++++++++--- .../Full/game/shaders/common/lighting.hlsl | 43 ++++++++++++++++--- .../advanced/dbgLightColorVisualizeP.hlsl | 6 +-- .../advanced/dbgLightSpecularVisualizeP.hlsl | 4 +- .../advanced/gl/dbgLightColorVisualizeP.glsl | 8 ++-- .../gl/dbgLightSpecularVisualizeP.glsl | 6 +-- .../lighting/advanced/gl/pointLightP.glsl | 17 +++++++- .../lighting/advanced/gl/spotLightP.glsl | 18 +++++++- .../lighting/advanced/gl/vectorLightP.glsl | 29 ++++++++----- .../common/lighting/advanced/pointLightP.hlsl | 18 +++++++- .../common/lighting/advanced/spotLightP.hlsl | 16 ++++++- .../lighting/advanced/vectorLightP.hlsl | 19 ++++++-- 13 files changed, 203 insertions(+), 50 deletions(-) diff --git a/Templates/Full/game/core/scripts/client/lighting/advanced/shaders.cs b/Templates/Full/game/core/scripts/client/lighting/advanced/shaders.cs index 2e73b6569..eaf7f70b8 100644 --- a/Templates/Full/game/core/scripts/client/lighting/advanced/shaders.cs +++ b/Templates/Full/game/core/scripts/client/lighting/advanced/shaders.cs @@ -36,8 +36,11 @@ new GFXStateBlockData( AL_VectorLightState ) samplersDefined = true; samplerStates[0] = SamplerClampPoint; // G-buffer + mSamplerNames[0] = "prePassBuffer"; samplerStates[1] = SamplerClampPoint; // Shadow Map (Do not change this to linear, as all cards can not filter equally.) + mSamplerNames[1] = "shadowMap"; samplerStates[2] = SamplerClampLinear; // SSAO Mask + mSamplerNames[2] = "ssaoMask"; samplerStates[3] = SamplerWrapPoint; // Random Direction Map cullDefined = true; @@ -66,7 +69,9 @@ new ShaderData( AL_VectorLightShader ) samplerNames[2] = "$dynamicShadowMap"; samplerNames[3] = "$ssaoMask"; samplerNames[4] = "$gTapRotationTex"; - + samplerNames[5] = "$lightBuffer"; + samplerNames[6] = "$colorBuffer"; + samplerNames[7] = "$matInfoBuffer"; pixVersion = 3.0; }; @@ -78,7 +83,10 @@ new CustomMaterial( AL_VectorLightMaterial ) sampler["prePassBuffer"] = "#prepass"; sampler["shadowMap"] = "$dynamiclight"; sampler["dynamicShadowMap"] = "$dynamicShadowMap"; - sampler["ssaoMask"] = "#ssaoMask"; + sampler["ssaoMask"] = "#ssaoMask"; + sampler["lightBuffer"] = "#lightinfo"; + sampler["colorBuffer"] = "#color"; + sampler["matInfoBuffer"] = "#matinfo"; target = "lightinfo"; @@ -103,7 +111,9 @@ new GFXStateBlockData( AL_ConvexLightState ) samplersDefined = true; samplerStates[0] = SamplerClampPoint; // G-buffer + mSamplerNames[0] = "prePassBuffer"; samplerStates[1] = SamplerClampPoint; // Shadow Map (Do not use linear, these are perspective projections) + mSamplerNames[1] = "shadowMap"; samplerStates[2] = SamplerClampLinear; // Cookie Map samplerStates[3] = SamplerWrapPoint; // Random Direction Map @@ -133,6 +143,9 @@ new ShaderData( AL_PointLightShader ) samplerNames[2] = "$dynamicShadowMap"; samplerNames[3] = "$cookieMap"; samplerNames[4] = "$gTapRotationTex"; + samplerNames[5] = "$lightBuffer"; + samplerNames[6] = "$colorBuffer"; + samplerNames[7] = "$matInfoBuffer"; pixVersion = 3.0; }; @@ -146,6 +159,9 @@ new CustomMaterial( AL_PointLightMaterial ) sampler["shadowMap"] = "$dynamiclight"; sampler["dynamicShadowMap"] = "$dynamicShadowMap"; sampler["cookieMap"] = "$dynamiclightmask"; + sampler["lightBuffer"] = "#lightinfo"; + sampler["colorBuffer"] = "#color"; + sampler["matInfoBuffer"] = "#matinfo"; target = "lightinfo"; @@ -166,6 +182,9 @@ new ShaderData( AL_SpotLightShader ) samplerNames[2] = "$dynamicShadowMap"; samplerNames[3] = "$cookieMap"; samplerNames[4] = "$gTapRotationTex"; + samplerNames[5] = "$lightBuffer"; + samplerNames[6] = "$colorBuffer"; + samplerNames[7] = "$matInfoBuffer"; pixVersion = 3.0; }; @@ -179,6 +198,9 @@ new CustomMaterial( AL_SpotLightMaterial ) sampler["shadowMap"] = "$dynamiclight"; sampler["dynamicShadowMap"] = "$dynamicShadowMap"; sampler["cookieMap"] = "$dynamiclightmask"; + sampler["lightBuffer"] = "#lightinfo"; + sampler["colorBuffer"] = "#color"; + sampler["matInfoBuffer"] = "#matinfo"; target = "lightinfo"; diff --git a/Templates/Full/game/shaders/common/gl/lighting.glsl b/Templates/Full/game/shaders/common/gl/lighting.glsl index eb1c9b355..804ab1e3b 100644 --- a/Templates/Full/game/shaders/common/gl/lighting.glsl +++ b/Templates/Full/game/shaders/common/gl/lighting.glsl @@ -20,6 +20,7 @@ // IN THE SOFTWARE. //----------------------------------------------------------------------------- +#include "./torque.glsl" #ifndef TORQUE_SHADERGEN @@ -207,14 +208,42 @@ void compute4Lights( vec3 wsView, /// float AL_CalcSpecular( vec3 toLight, vec3 normal, vec3 toEye ) { - #ifdef PHONG_SPECULAR - // (R.V)^c - float specVal = dot( normalize( -reflect( toLight, normal ) ), toEye ); - #else - // (N.H)^c [Blinn-Phong, TGEA style, default] - float specVal = dot( normal, normalize( toLight + toEye ) ); - #endif + // (R.V)^c + float specVal = dot( normalize( -reflect( toLight, normal ) ), toEye ); // Return the specular factor. return pow( max( specVal, 0.00001f ), AL_ConstantSpecularPower ); } + +/// The output for Deferred Lighting +/// +/// @param toLight Normalized vector representing direction from the pixel +/// being lit, to the light source, in world space. +/// +/// @param normal Normalized surface normal. +/// +/// @param toEye The normalized vector representing direction from the pixel +/// being lit to the camera. +/// +vec4 AL_DeferredOutput( + vec3 lightColor, + vec3 diffuseColor, + vec4 matInfo, + vec4 ambient, + float specular, + float shadowAttenuation) +{ + vec3 specularColor = vec3(specular); + bool metalness = getFlag(matInfo.r, 3); + if ( metalness ) + { + specularColor = 0.04 * (1 - specular) + diffuseColor * specular; + } + + //specular = color * map * spec^gloss + float specularOut = (specularColor * matInfo.b * min(pow(max(specular,1.0f), max((matInfo.a / AL_ConstantSpecularPower),1.0f)),matInfo.a)).r; + + lightColor *= vec3(shadowAttenuation); + lightColor += ambient.rgb; + return vec4(lightColor.rgb, specularOut); +} diff --git a/Templates/Full/game/shaders/common/lighting.hlsl b/Templates/Full/game/shaders/common/lighting.hlsl index ec8129e94..a2c753618 100644 --- a/Templates/Full/game/shaders/common/lighting.hlsl +++ b/Templates/Full/game/shaders/common/lighting.hlsl @@ -20,6 +20,7 @@ // IN THE SOFTWARE. //----------------------------------------------------------------------------- +#include "./torque.hlsl" #ifndef TORQUE_SHADERGEN @@ -207,14 +208,42 @@ void compute4Lights( float3 wsView, /// float AL_CalcSpecular( float3 toLight, float3 normal, float3 toEye ) { - #ifdef PHONG_SPECULAR - // (R.V)^c - float specVal = dot( normalize( -reflect( toLight, normal ) ), toEye ); - #else - // (N.H)^c [Blinn-Phong, TGEA style, default] - float specVal = dot( normal, normalize( toLight + toEye ) ); - #endif + // (R.V)^c + float specVal = dot( normalize( -reflect( toLight, normal ) ), toEye ); // Return the specular factor. return pow( max( specVal, 0.00001f ), AL_ConstantSpecularPower ); } + +/// The output for Deferred Lighting +/// +/// @param toLight Normalized vector representing direction from the pixel +/// being lit, to the light source, in world space. +/// +/// @param normal Normalized surface normal. +/// +/// @param toEye The normalized vector representing direction from the pixel +/// being lit to the camera. +/// +float4 AL_DeferredOutput( + float3 lightColor, + float3 diffuseColor, + float4 matInfo, + float4 ambient, + float specular, + float shadowAttenuation) +{ + float3 specularColor = float3(specular, specular, specular); + bool metalness = getFlag(matInfo.r, 3); + if ( metalness ) + { + specularColor = 0.04 * (1 - specular) + diffuseColor * specular; + } + + //specular = color * map * spec^gloss + float specularOut = (specularColor * matInfo.b * min(pow(specular, max(( matInfo.a/ AL_ConstantSpecularPower),1.0f)),matInfo.a)).r; + + lightColor *= shadowAttenuation; + lightColor += ambient.rgb; + return float4(lightColor.rgb, specularOut); +} diff --git a/Templates/Full/game/shaders/common/lighting/advanced/dbgLightColorVisualizeP.hlsl b/Templates/Full/game/shaders/common/lighting/advanced/dbgLightColorVisualizeP.hlsl index 487b4c740..e037ad8b9 100644 --- a/Templates/Full/game/shaders/common/lighting/advanced/dbgLightColorVisualizeP.hlsl +++ b/Templates/Full/game/shaders/common/lighting/advanced/dbgLightColorVisualizeP.hlsl @@ -27,8 +27,6 @@ float4 main( PFXVertToPix IN, uniform sampler2D lightPrePassTex : register(S0) ) : COLOR0 { - float3 lightcolor; - float nl_Att, specular; - lightinfoUncondition( tex2D( lightPrePassTex, IN.uv0 ), lightcolor, nl_Att, specular ); - return float4( lightcolor, 1.0 ); + float4 lightColor = tex2D( lightPrePassTex, IN.uv0 ); + return float4( lightColor.rgb, 1.0 ); } diff --git a/Templates/Full/game/shaders/common/lighting/advanced/dbgLightSpecularVisualizeP.hlsl b/Templates/Full/game/shaders/common/lighting/advanced/dbgLightSpecularVisualizeP.hlsl index edc25ed03..8e1074add 100644 --- a/Templates/Full/game/shaders/common/lighting/advanced/dbgLightSpecularVisualizeP.hlsl +++ b/Templates/Full/game/shaders/common/lighting/advanced/dbgLightSpecularVisualizeP.hlsl @@ -27,8 +27,6 @@ float4 main( PFXVertToPix IN, uniform sampler2D lightPrePassTex : register(S0) ) : COLOR0 { - float3 lightcolor; - float nl_Att, specular; - lightinfoUncondition( tex2D( lightPrePassTex, IN.uv0 ), lightcolor, nl_Att, specular ); + float specular = tex2D( lightPrePassTex, IN.uv0 ).a; return float4( specular, specular, specular, 1.0 ); } diff --git a/Templates/Full/game/shaders/common/lighting/advanced/gl/dbgLightColorVisualizeP.glsl b/Templates/Full/game/shaders/common/lighting/advanced/gl/dbgLightColorVisualizeP.glsl index 05645e193..501e261ca 100644 --- a/Templates/Full/game/shaders/common/lighting/advanced/gl/dbgLightColorVisualizeP.glsl +++ b/Templates/Full/game/shaders/common/lighting/advanced/gl/dbgLightColorVisualizeP.glsl @@ -24,14 +24,12 @@ #include "shadergen:/autogenConditioners.h" in vec2 uv0; -uniform sampler2D lightInfoBuffer; +uniform sampler2D lightPrePassTex; out vec4 OUT_col; void main() { - vec3 lightcolor; - float nl_Att, specular; - lightinfoUncondition( texture( lightInfoBuffer, uv0 ), lightcolor, nl_Att, specular ); - OUT_col = vec4( lightcolor, 1.0 ); + vec4 lightColor = texture( lightPrePassTex, uv0 ); + OUT_col = vec4( lightColor.rgb, 1.0 ); } \ No newline at end of file diff --git a/Templates/Full/game/shaders/common/lighting/advanced/gl/dbgLightSpecularVisualizeP.glsl b/Templates/Full/game/shaders/common/lighting/advanced/gl/dbgLightSpecularVisualizeP.glsl index 7e3e41ee9..c21c9b60f 100644 --- a/Templates/Full/game/shaders/common/lighting/advanced/gl/dbgLightSpecularVisualizeP.glsl +++ b/Templates/Full/game/shaders/common/lighting/advanced/gl/dbgLightSpecularVisualizeP.glsl @@ -24,14 +24,12 @@ #include "shadergen:/autogenConditioners.h" in vec2 uv0; -uniform sampler2D lightInfoBuffer; +uniform sampler2D lightPrePassTex; out vec4 OUT_col; void main() { - vec3 lightcolor; - float nl_Att, specular; - lightinfoUncondition( texture( lightInfoBuffer, uv0 ), lightcolor, nl_Att, specular ); + float specular = texture( lightPrePassTex, uv0 ).a; OUT_col = vec4( specular, specular, specular, 1.0 ); } \ No newline at end of file diff --git a/Templates/Full/game/shaders/common/lighting/advanced/gl/pointLightP.glsl b/Templates/Full/game/shaders/common/lighting/advanced/gl/pointLightP.glsl index 92c9369a7..8a1aae3ca 100644 --- a/Templates/Full/game/shaders/common/lighting/advanced/gl/pointLightP.glsl +++ b/Templates/Full/game/shaders/common/lighting/advanced/gl/pointLightP.glsl @@ -33,6 +33,7 @@ in vec4 wsEyeDir; in vec4 ssPos; in vec4 vsEyeDir; +in vec4 color; #ifdef USE_COOKIE_TEX @@ -111,6 +112,10 @@ uniform sampler2D prePassBuffer; uniform sampler2D dynamicShadowMap; #endif +uniform sampler2D lightBuffer; +uniform sampler2D colorBuffer; +uniform sampler2D matInfoBuffer; + uniform vec4 rtParams0; uniform vec3 lightPosition; @@ -133,6 +138,15 @@ void main() vec3 ssPos = ssPos.xyz / ssPos.w; vec2 uvScene = getUVFromSSPos( ssPos, rtParams0 ); + // Emissive. + vec4 matInfo = texture( matInfoBuffer, uvScene ); + bool emissive = getFlag( matInfo.r, 0 ); + if ( emissive ) + { + OUT_col = vec4(0.0, 0.0, 0.0, 0.0); + return; + } + // Sample/unpack the normal/z data vec4 prepassSample = prepassUncondition( prePassBuffer, uvScene ); vec3 normal = prepassSample.rgb; @@ -244,5 +258,6 @@ void main() addToResult = ( 1.0 - shadowed ) * abs(lightMapParams); } - OUT_col = lightinfoCondition( lightColorOut, Sat_NL_Att, specular, addToResult ); + vec4 colorSample = texture( colorBuffer, uvScene ); + OUT_col = AL_DeferredOutput(lightColorOut, colorSample.rgb, matInfo, addToResult, specular, Sat_NL_Att); } diff --git a/Templates/Full/game/shaders/common/lighting/advanced/gl/spotLightP.glsl b/Templates/Full/game/shaders/common/lighting/advanced/gl/spotLightP.glsl index 29c278508..e7f3e88a7 100644 --- a/Templates/Full/game/shaders/common/lighting/advanced/gl/spotLightP.glsl +++ b/Templates/Full/game/shaders/common/lighting/advanced/gl/spotLightP.glsl @@ -32,10 +32,12 @@ in vec4 wsEyeDir; in vec4 ssPos; in vec4 vsEyeDir; +in vec4 color; #define IN_wsEyeDir wsEyeDir #define IN_ssPos ssPos #define IN_vsEyeDir vsEyeDir +#define IN_color color #ifdef USE_COOKIE_TEX @@ -48,6 +50,10 @@ uniform sampler2D prePassBuffer; uniform sampler2D shadowMap; uniform sampler2D dynamicShadowMap; +uniform sampler2D lightBuffer; +uniform sampler2D colorBuffer; +uniform sampler2D matInfoBuffer; + uniform vec4 rtParams0; uniform vec3 lightPosition; @@ -74,6 +80,15 @@ void main() vec3 ssPos = IN_ssPos.xyz / IN_ssPos.w; vec2 uvScene = getUVFromSSPos( ssPos, rtParams0 ); + // Emissive. + vec4 matInfo = texture( matInfoBuffer, uvScene ); + bool emissive = getFlag( matInfo.r, 0 ); + if ( emissive ) + { + OUT_col = vec4(0.0, 0.0, 0.0, 0.0); + return; + } + // Sample/unpack the normal/z data vec4 prepassSample = prepassUncondition( prePassBuffer, uvScene ); vec3 normal = prepassSample.rgb; @@ -180,5 +195,6 @@ void main() addToResult = ( 1.0 - shadowed ) * abs(lightMapParams); } - OUT_col = lightinfoCondition( lightColorOut, Sat_NL_Att, specular, addToResult ); + vec4 colorSample = texture( colorBuffer, uvScene ); + OUT_col = AL_DeferredOutput(lightColorOut, colorSample.rgb, matInfo, addToResult, specular, Sat_NL_Att); } diff --git a/Templates/Full/game/shaders/common/lighting/advanced/gl/vectorLightP.glsl b/Templates/Full/game/shaders/common/lighting/advanced/gl/vectorLightP.glsl index 4eb4973a3..608524a5a 100644 --- a/Templates/Full/game/shaders/common/lighting/advanced/gl/vectorLightP.glsl +++ b/Templates/Full/game/shaders/common/lighting/advanced/gl/vectorLightP.glsl @@ -39,10 +39,13 @@ uniform sampler2D dynamicShadowMap; #ifdef USE_SSAO_MASK uniform sampler2D ssaoMask ; -uniform vec4 rtParams2; +uniform vec4 rtParams3; #endif -uniform sampler2D prePassBuffer; +uniform sampler2D prePassBuffer; +uniform sampler2D lightBuffer; +uniform sampler2D colorBuffer; +uniform sampler2D matInfoBuffer; uniform vec3 lightDirection; uniform vec4 lightColor; uniform float lightBrightness; @@ -189,7 +192,16 @@ vec4 AL_VectorLightShadowCast( sampler2D _sourceshadowMap, out vec4 OUT_col; void main() -{ +{ + // Emissive. + float4 matInfo = texture( matInfoBuffer, uv0 ); + bool emissive = getFlag( matInfo.r, 0 ); + if ( emissive ) + { + OUT_col = vec4(1.0, 1.0, 1.0, 0.0); + return; + } + // Sample/unpack the normal/z data vec4 prepassSample = prepassUncondition( prePassBuffer, uv0 ); vec3 normal = prepassSample.rgb; @@ -228,8 +240,6 @@ void main() shadowSoftness, dotNL, overDarkPSSM); - - vec4 dynamic_shadowed_colors = AL_VectorLightShadowCast( dynamicShadowMap, uv0.xy, dynamicWorldToLightProj, @@ -242,14 +252,13 @@ void main() shadowSoftness, dotNL, overDarkPSSM); - float static_shadowed = static_shadowed_colors.a; float dynamic_shadowed = dynamic_shadowed_colors.a; #ifdef PSSM_DEBUG_RENDER debugColor = static_shadowed_colors.rgb*0.5+dynamic_shadowed_colors.rgb*0.5; #endif - + // Fade out the shadow at the end of the range. vec4 zDist = vec4(zNearFarInvNearFar.x + zNearFarInvNearFar.y * depth); float fadeOutAmt = ( zDist.x - fadeStartLength.x ) * fadeStartLength.y; @@ -295,7 +304,7 @@ void main() // Sample the AO texture. #ifdef USE_SSAO_MASK - float ao = 1.0 - texture( ssaoMask, viewportCoordToRenderTarget( uv0.xy, rtParams2 ) ).r; + float ao = 1.0 - texture( ssaoMask, viewportCoordToRenderTarget( uv0.xy, rtParams3 ) ).r; addToResult *= ao; #endif @@ -303,6 +312,6 @@ void main() lightColorOut = debugColor; #endif - OUT_col = lightinfoCondition( lightColorOut, Sat_NL_Att, specular, addToResult ); - + vec4 colorSample = texture( colorBuffer, uv0 ); + OUT_col = AL_DeferredOutput(lightColorOut, colorSample.rgb, matInfo, addToResult, specular, Sat_NL_Att); } diff --git a/Templates/Full/game/shaders/common/lighting/advanced/pointLightP.hlsl b/Templates/Full/game/shaders/common/lighting/advanced/pointLightP.hlsl index 48c0d76e3..9051ff09d 100644 --- a/Templates/Full/game/shaders/common/lighting/advanced/pointLightP.hlsl +++ b/Templates/Full/game/shaders/common/lighting/advanced/pointLightP.hlsl @@ -34,6 +34,7 @@ struct ConvexConnectP float4 wsEyeDir : TEXCOORD0; float4 ssPos : TEXCOORD1; float4 vsEyeDir : TEXCOORD2; + float4 color : COLOR0; }; @@ -117,6 +118,10 @@ float4 main( ConvexConnectP IN, uniform sampler2D dynamicShadowMap : register(S2), #endif + uniform sampler2D lightBuffer : register(S5), + uniform sampler2D colorBuffer : register(S6), + uniform sampler2D matInfoBuffer : register(S7), + uniform float4 rtParams0, uniform float3 lightPosition, @@ -136,7 +141,15 @@ float4 main( ConvexConnectP IN, // Compute scene UV float3 ssPos = IN.ssPos.xyz / IN.ssPos.w; float2 uvScene = getUVFromSSPos( ssPos, rtParams0 ); - + + // Emissive. + float4 matInfo = tex2D( matInfoBuffer, uvScene ); + bool emissive = getFlag( matInfo.r, 0 ); + if ( emissive ) + { + return float4(0.0, 0.0, 0.0, 0.0); + } + // Sample/unpack the normal/z data float4 prepassSample = prepassUncondition( prePassBuffer, uvScene ); float3 normal = prepassSample.rgb; @@ -250,5 +263,6 @@ float4 main( ConvexConnectP IN, addToResult = ( 1.0 - shadowed ) * abs(lightMapParams); } - return lightinfoCondition( lightColorOut, Sat_NL_Att, specular, addToResult ); + float4 colorSample = tex2D( colorBuffer, uvScene ); + return AL_DeferredOutput(lightColorOut, colorSample.rgb, matInfo, addToResult, specular, Sat_NL_Att); } diff --git a/Templates/Full/game/shaders/common/lighting/advanced/spotLightP.hlsl b/Templates/Full/game/shaders/common/lighting/advanced/spotLightP.hlsl index 33c7f333e..226b9cfea 100644 --- a/Templates/Full/game/shaders/common/lighting/advanced/spotLightP.hlsl +++ b/Templates/Full/game/shaders/common/lighting/advanced/spotLightP.hlsl @@ -34,6 +34,7 @@ struct ConvexConnectP float4 wsEyeDir : TEXCOORD0; float4 ssPos : TEXCOORD1; float4 vsEyeDir : TEXCOORD2; + float4 color : COLOR0; }; #ifdef USE_COOKIE_TEX @@ -50,6 +51,10 @@ float4 main( ConvexConnectP IN, uniform sampler2D shadowMap : register(S1), uniform sampler2D dynamicShadowMap : register(S2), + uniform sampler2D lightBuffer : register(S5), + uniform sampler2D colorBuffer : register(S6), + uniform sampler2D matInfoBuffer : register(S7), + uniform float4 rtParams0, uniform float3 lightPosition, @@ -72,6 +77,14 @@ float4 main( ConvexConnectP IN, float3 ssPos = IN.ssPos.xyz / IN.ssPos.w; float2 uvScene = getUVFromSSPos( ssPos, rtParams0 ); + // Emissive. + float4 matInfo = tex2D( matInfoBuffer, uvScene ); + bool emissive = getFlag( matInfo.r, 0 ); + if ( emissive ) + { + return float4(0.0, 0.0, 0.0, 0.0); + } + // Sample/unpack the normal/z data float4 prepassSample = prepassUncondition( prePassBuffer, uvScene ); float3 normal = prepassSample.rgb; @@ -179,5 +192,6 @@ float4 main( ConvexConnectP IN, addToResult = ( 1.0 - shadowed ) * abs(lightMapParams); } - return lightinfoCondition( lightColorOut, Sat_NL_Att, specular, addToResult ); + float4 colorSample = tex2D( colorBuffer, uvScene ); + return AL_DeferredOutput(lightColorOut, colorSample.rgb, matInfo, addToResult, specular, Sat_NL_Att); } diff --git a/Templates/Full/game/shaders/common/lighting/advanced/vectorLightP.hlsl b/Templates/Full/game/shaders/common/lighting/advanced/vectorLightP.hlsl index 2d719587e..896576564 100644 --- a/Templates/Full/game/shaders/common/lighting/advanced/vectorLightP.hlsl +++ b/Templates/Full/game/shaders/common/lighting/advanced/vectorLightP.hlsl @@ -157,6 +157,10 @@ float4 main( FarFrustumQuadConnectP IN, uniform sampler2D prePassBuffer : register(S0), + uniform sampler2D lightBuffer : register(S5), + uniform sampler2D colorBuffer : register(S6), + uniform sampler2D matInfoBuffer : register(S7), + uniform float3 lightDirection, uniform float4 lightColor, uniform float lightBrightness, @@ -190,7 +194,15 @@ float4 main( FarFrustumQuadConnectP IN, uniform float4 dynamicFarPlaneScalePSSM ) : COLOR0 -{ +{ + // Emissive. + float4 matInfo = tex2D( matInfoBuffer, IN.uv0 ); + bool emissive = getFlag( matInfo.r, 0 ); + if ( emissive ) + { + return float4(1.0, 1.0, 1.0, 0.0); + } + // Sample/unpack the normal/z data float4 prepassSample = prepassUncondition( prePassBuffer, IN.uv0 ); float3 normal = prepassSample.rgb; @@ -229,7 +241,6 @@ float4 main( FarFrustumQuadConnectP IN, shadowSoftness, dotNL, overDarkPSSM); - float4 dynamic_shadowed_colors = AL_VectorLightShadowCast( dynamicShadowMap, IN.uv0.xy, dynamicWorldToLightProj, @@ -276,6 +287,7 @@ float4 main( FarFrustumQuadConnectP IN, float Sat_NL_Att = saturate( dotNL * shadowed ) * lightBrightness; float3 lightColorOut = lightMapParams.rgb * lightColor.rgb; + float4 addToResult = (lightAmbient * (1 - ambientCameraFactor)) + ( lightAmbient * ambientCameraFactor * saturate(dot(normalize(-IN.vsEyeRay), normal)) ); // TODO: This needs to be removed when lightmapping is disabled @@ -303,5 +315,6 @@ float4 main( FarFrustumQuadConnectP IN, lightColorOut = debugColor; #endif - return lightinfoCondition( lightColorOut, Sat_NL_Att, specular, addToResult ); + float4 colorSample = tex2D( colorBuffer, IN.uv0 ); + return AL_DeferredOutput(lightColorOut, colorSample.rgb, matInfo, addToResult, specular, Sat_NL_Att); } From 8c5810adad0a828234f634eee1891690ba60f598 Mon Sep 17 00:00:00 2001 From: Azaezel Date: Tue, 16 Feb 2016 02:50:49 -0600 Subject: [PATCH 080/109] The final step (barring any overlooked missing bits, requested refactors, and of course, rolling in dependencies already submitted as PRs) consists of: renderPrePassMgr.cpp related: A) shifting .addFeature( MFT_XYZ); calls from ProcessedShaderMaterial::_determineFeatures to ProcessedPrePassMaterial::_determineFeatures B) mimicking the "// set the XXX if different" entries from RenderMeshMgr::render in RenderPrePassMgr::render C) fleshing out ProcessedPrePassMaterial::getNumStages() so that it shares a 1:1 correlation with ProcessedShaderMaterial::getNumStages() D) causing inline void Swizzle::ToBuffer( void *destination, const void *source, const dsize_t size ) to silently fail rather than fatally assert if a source or destination buffer is not yet ready to be filled. (support for #customTarget scripted render targets) Reflections: A) removing reflectRenderState.disableAdvancedLightingBins(true); entries. this would otherwise early out from prepass and provide no color data whatsoever. B) removing the fd.features.addFeature( MFT_ForwardShading ); entry forcing all materials to be forward lit when reflected. C) 2 things best described bluntly as working hacks: C1) when reflected, a scattersky is rotated PI along it's z then x axis in order to draw properly. C2) along similar lines, in terraincellmaterial, we shut off culling if it's a prepass material. Skies: scattersky is given a pair of rotations for reflection purposes, all sky objects are given a z value for depth testing. --- Engine/source/core/util/swizzle.h | 3 +- Engine/source/environment/basicClouds.cpp | 2 +- Engine/source/environment/cloudLayer.cpp | 2 +- Engine/source/environment/decalRoad.cpp | 2 +- Engine/source/environment/scatterSky.cpp | 11 +++- Engine/source/environment/skyBox.cpp | 3 +- .../source/gfx/D3D9/gfxD3D9CardProfiler.cpp | 2 + Engine/source/gfx/D3D9/pc/gfxPCD3D9Target.cpp | 5 ++ Engine/source/gfx/gfxDevice.h | 5 ++ Engine/source/gfx/gl/gfxGLStateBlock.cpp | 8 +++ Engine/source/gfx/gl/gfxGLTextureTarget.cpp | 18 ++++++ Engine/source/gfx/gl/gfxGLWindowTarget.cpp | 1 + .../glsl/advancedLightingFeaturesGLSL.cpp | 30 +++++----- .../advanced/glsl/gBufferConditionerGLSL.cpp | 4 +- .../hlsl/advancedLightingFeaturesHLSL.cpp | 16 ++--- .../advanced/hlsl/gBufferConditionerHLSL.cpp | 2 +- Engine/source/materials/matTextureTarget.cpp | 11 +++- .../source/materials/materialDefinition.cpp | 20 ++++--- Engine/source/materials/materialDefinition.h | 8 ++- Engine/source/materials/processedMaterial.cpp | 8 --- .../materials/processedShaderMaterial.cpp | 58 ++++++------------- .../materials/processedShaderMaterial.h | 3 + .../renderInstance/renderBinManager.cpp | 6 +- .../source/renderInstance/renderBinManager.h | 3 + .../source/renderInstance/renderGlowMgr.cpp | 2 + .../source/renderInstance/renderMeshMgr.cpp | 8 +++ .../source/renderInstance/renderObjectMgr.cpp | 6 ++ .../renderInstance/renderPassManager.cpp | 1 + .../source/renderInstance/renderPassManager.h | 1 + .../renderInstance/renderTerrainMgr.cpp | 5 ++ Engine/source/scene/reflectionManager.cpp | 2 +- Engine/source/scene/reflectionMatHook.cpp | 2 - Engine/source/scene/reflector.cpp | 3 - .../source/shaderGen/GLSL/accuFeatureGLSL.cpp | 4 +- .../source/shaderGen/HLSL/accuFeatureHLSL.cpp | 4 +- .../postFx/hdr/gl/finalPassCombineP.glsl | 9 ++- Templates/Full/game/art/prefabs/fire.prefab | 49 ++++++++++++++++ .../art/shapes/trees/defaulttree/materials.cs | 7 ++- .../game/art/skies/Desert_Sky/materials.cs | 1 + .../Full/game/art/skies/night/materials.cs | 2 + .../game/core/art/skies/blank/materials.cs | 3 + .../scripts/client/lighting/advanced/init.cs | 7 +++ .../core/scripts/client/postFx/caustics.cs | 2 +- .../game/core/scripts/client/postFx/hdr.cs | 3 + .../core/scripts/client/postFx/turbulence.cs | 2 +- .../game/core/scripts/client/scatterSky.cs | 4 +- .../game/shaders/common/basicCloudsV.hlsl | 1 + .../Full/game/shaders/common/cloudLayerV.hlsl | 2 +- .../game/shaders/common/gl/basicCloudsV.glsl | 1 + .../game/shaders/common/gl/cloudLayerV.glsl | 1 + .../game/shaders/common/gl/scatterSkyP.glsl | 3 + .../advanced/deferredColorShaderP.hlsl | 44 ++++++++++++++ .../advanced/gl/dbgDepthVisualizeP.glsl | 4 +- .../advanced/gl/dbgNormalVisualizeP.glsl | 4 +- .../advanced/gl/deferredColorShaderP.glsl | 37 ++++++++++++ .../common/postFx/hdr/finalPassCombineP.hlsl | 7 ++- .../postFx/hdr/gl/finalPassCombineP.glsl | 6 +- .../Full/game/shaders/common/scatterSkyP.hlsl | 2 +- 58 files changed, 353 insertions(+), 117 deletions(-) create mode 100644 Templates/Full/game/art/prefabs/fire.prefab create mode 100644 Templates/Full/game/shaders/common/lighting/advanced/deferredColorShaderP.hlsl create mode 100644 Templates/Full/game/shaders/common/lighting/advanced/gl/deferredColorShaderP.glsl diff --git a/Engine/source/core/util/swizzle.h b/Engine/source/core/util/swizzle.h index abbff288f..0f1d583f6 100644 --- a/Engine/source/core/util/swizzle.h +++ b/Engine/source/core/util/swizzle.h @@ -120,8 +120,7 @@ inline void Swizzle::ToBuffer( void *destination, const void *sour { // TODO: OpenMP? AssertFatal( size % ( sizeof( T ) * mapLength ) == 0, "Bad buffer size for swizzle, see docs." ); - AssertFatal( destination != NULL, "Swizzle::ToBuffer - got a NULL destination pointer!" ); - AssertFatal( source != NULL, "Swizzle::ToBuffer - got a NULL source pointer!" ); + if (!destination || !source) return; T *dest = reinterpret_cast( destination ); const T *src = reinterpret_cast( source ); diff --git a/Engine/source/environment/basicClouds.cpp b/Engine/source/environment/basicClouds.cpp index 185aa3a97..979579c2f 100644 --- a/Engine/source/environment/basicClouds.cpp +++ b/Engine/source/environment/basicClouds.cpp @@ -141,7 +141,7 @@ bool BasicClouds::onAdd() GFXStateBlockDesc desc; desc.setCullMode( GFXCullNone ); desc.setBlend( true ); - desc.setZReadWrite( false, false ); + desc.setZReadWrite( true, false ); desc.samplersDefined = true; desc.samplers[0].addressModeU = GFXAddressWrap; desc.samplers[0].addressModeV = GFXAddressWrap; diff --git a/Engine/source/environment/cloudLayer.cpp b/Engine/source/environment/cloudLayer.cpp index dca9faf00..3fe02368f 100644 --- a/Engine/source/environment/cloudLayer.cpp +++ b/Engine/source/environment/cloudLayer.cpp @@ -161,7 +161,7 @@ bool CloudLayer::onAdd() GFXStateBlockDesc desc; desc.setCullMode( GFXCullNone ); desc.setBlend( true ); - desc.setZReadWrite( false, false ); + desc.setZReadWrite( true, false ); desc.samplersDefined = true; desc.samplers[0].addressModeU = GFXAddressWrap; desc.samplers[0].addressModeV = GFXAddressWrap; diff --git a/Engine/source/environment/decalRoad.cpp b/Engine/source/environment/decalRoad.cpp index 3cde83149..1cea7ed4a 100644 --- a/Engine/source/environment/decalRoad.cpp +++ b/Engine/source/environment/decalRoad.cpp @@ -732,7 +732,7 @@ void DecalRoad::prepRenderImage( SceneRenderState* state ) MathUtils::getZBiasProjectionMatrix( gDecalBias, frustum, tempMat ); coreRI.projection = tempMat; - coreRI.type = RenderPassManager::RIT_Decal; + coreRI.type = RenderPassManager::RIT_DecalRoad; coreRI.vertBuff = &mVB; coreRI.primBuff = &mPB; coreRI.matInst = matInst; diff --git a/Engine/source/environment/scatterSky.cpp b/Engine/source/environment/scatterSky.cpp index 328a64187..dbdeef48b 100644 --- a/Engine/source/environment/scatterSky.cpp +++ b/Engine/source/environment/scatterSky.cpp @@ -955,12 +955,21 @@ void ScatterSky::_render( ObjectRenderInst *ri, SceneRenderState *state, BaseMat Point3F camPos2 = state->getCameraPosition(); MatrixF xfm(true); - xfm.setPosition(camPos2 - Point3F( 0, 0, mZOffset)); + GFX->multWorld(xfm); MatrixF xform(proj);//GFX->getProjectionMatrix()); xform *= GFX->getViewMatrix(); xform *= GFX->getWorldMatrix(); + if(state->isReflectPass()) + { + static MatrixF rotMat(EulerF(0.0, 0.0, M_PI_F)); + xform.mul(rotMat); + rotMat.set(EulerF(M_PI_F, 0.0, 0.0)); + xform.mul(rotMat); + } + xform.setPosition(xform.getPosition() - Point3F(0, 0, mZOffset)); + mShaderConsts->setSafe( mModelViewProjSC, xform ); mShaderConsts->setSafe( mMiscSC, miscParams ); mShaderConsts->setSafe( mSphereRadiiSC, sphereRadii ); diff --git a/Engine/source/environment/skyBox.cpp b/Engine/source/environment/skyBox.cpp index 6ed2174e5..83c9bb6e7 100644 --- a/Engine/source/environment/skyBox.cpp +++ b/Engine/source/environment/skyBox.cpp @@ -599,7 +599,8 @@ void SkyBox::_initMaterial() // We want to disable culling and z write. GFXStateBlockDesc desc; - desc.setCullMode( GFXCullCW ); + desc.setCullMode( GFXCullNone ); + desc.setBlend( true ); desc.setZReadWrite( true, false ); mMatInstance->addStateBlockDesc( desc ); diff --git a/Engine/source/gfx/D3D9/gfxD3D9CardProfiler.cpp b/Engine/source/gfx/D3D9/gfxD3D9CardProfiler.cpp index 988e3a4e2..c6b169614 100644 --- a/Engine/source/gfx/D3D9/gfxD3D9CardProfiler.cpp +++ b/Engine/source/gfx/D3D9/gfxD3D9CardProfiler.cpp @@ -87,9 +87,11 @@ void GFXD3D9CardProfiler::setupCardCapabilities() bool canDoFourStageDetailBlend = ( caps.TextureOpCaps & D3DTEXOPCAPS_SUBTRACT ) && ( caps.PrimitiveMiscCaps & D3DPMISCCAPS_TSSARGTEMP ) && ( caps.MaxTextureBlendStages > 3 ); + bool canDoIndependentMrtBitDepth = (caps.PrimitiveMiscCaps & D3DPMISCCAPS_MRTINDEPENDENTBITDEPTHS ? 1 : 0 ); setCapability( "lerpDetailBlend", canDoLERPDetailBlend ); setCapability( "fourStageDetailBlend", canDoFourStageDetailBlend ); + setCapability( "independentMrtBitDepth", canDoIndependentMrtBitDepth); } bool GFXD3D9CardProfiler::_queryCardCap(const String &query, U32 &foundResult) diff --git a/Engine/source/gfx/D3D9/pc/gfxPCD3D9Target.cpp b/Engine/source/gfx/D3D9/pc/gfxPCD3D9Target.cpp index d545b5ba2..415801263 100644 --- a/Engine/source/gfx/D3D9/pc/gfxPCD3D9Target.cpp +++ b/Engine/source/gfx/D3D9/pc/gfxPCD3D9Target.cpp @@ -31,6 +31,9 @@ #include "gfx/gfxDebugEvent.h" #include "windowManager/win32/win32Window.h" +#ifndef _GFXDEVICE_H_ +#include "gfx/gfxDevice.h" +#endif GFXPCD3D9TextureTarget::GFXPCD3D9TextureTarget() : mTargetSize( Point2I::Zero ), @@ -451,6 +454,7 @@ void GFXPCD3D9WindowTarget::createAdditionalSwapChain() void GFXPCD3D9WindowTarget::resetMode() { + GFX->beginReset(); mWindow->setSuppressReset(true); if (mSwapChain) @@ -509,6 +513,7 @@ void GFXPCD3D9WindowTarget::zombify() void GFXPCD3D9WindowTarget::resurrect() { + GFX->beginReset(); if(mImplicit) { setImplicitSwapChain(); diff --git a/Engine/source/gfx/gfxDevice.h b/Engine/source/gfx/gfxDevice.h index b9ff01e8c..44ca1d767 100644 --- a/Engine/source/gfx/gfxDevice.h +++ b/Engine/source/gfx/gfxDevice.h @@ -302,6 +302,7 @@ protected: /// This will allow querying to see if a device is initialized and ready to /// have operations performed on it. bool mInitialized; + bool mReset; /// This is called before this, or any other device, is deleted in the global destroy() /// method. It allows the device to clean up anything while everything is still valid. @@ -326,6 +327,10 @@ public: /// @see endScene bool canCurrentlyRender() const { return mCanCurrentlyRender; } + bool recentlyReset(){ return mReset; } + void beginReset(){ mReset = true; } + void finalizeReset(){ mReset = false; } + void setAllowRender( bool render ) { mAllowRender = render; } inline bool allowRender() const { return mAllowRender; } diff --git a/Engine/source/gfx/gl/gfxGLStateBlock.cpp b/Engine/source/gfx/gl/gfxGLStateBlock.cpp index a1af2910c..34f816dc9 100644 --- a/Engine/source/gfx/gl/gfxGLStateBlock.cpp +++ b/Engine/source/gfx/gl/gfxGLStateBlock.cpp @@ -107,6 +107,14 @@ void GFXGLStateBlock::activate(const GFXGLStateBlock* oldState) if(STATE_CHANGE(blendOp)) glBlendEquation(GFXGLBlendOp[mDesc.blendOp]); + if (mDesc.separateAlphaBlendEnable == true) + { + if (STATE_CHANGE(separateAlphaBlendSrc) || STATE_CHANGE(separateAlphaBlendDest)) + glBlendFuncSeparate(GFXGLBlend[mDesc.blendSrc], GFXGLBlend[mDesc.blendDest], GFXGLBlend[mDesc.separateAlphaBlendSrc], GFXGLBlend[mDesc.separateAlphaBlendDest]); + if (STATE_CHANGE(separateAlphaBlendOp)) + glBlendEquationSeparate(GFXGLBlendOp[mDesc.blendOp], GFXGLBlendOp[mDesc.separateAlphaBlendOp]); + } + // Color write masks if(STATE_CHANGE(colorWriteRed) || STATE_CHANGE(colorWriteBlue) || STATE_CHANGE(colorWriteGreen) || STATE_CHANGE(colorWriteAlpha)) glColorMask(mDesc.colorWriteRed, mDesc.colorWriteBlue, mDesc.colorWriteGreen, mDesc.colorWriteAlpha); diff --git a/Engine/source/gfx/gl/gfxGLTextureTarget.cpp b/Engine/source/gfx/gl/gfxGLTextureTarget.cpp index ddb308adc..202265107 100644 --- a/Engine/source/gfx/gl/gfxGLTextureTarget.cpp +++ b/Engine/source/gfx/gl/gfxGLTextureTarget.cpp @@ -163,6 +163,10 @@ void _GFXGLTextureTargetFBOImpl::applyState() PRESERVE_FRAMEBUFFER(); glBindFramebuffer(GL_FRAMEBUFFER, mFramebuffer); + bool drawbufs[16]; + int bufsize = 0; + for (int i = 0; i < 16; i++) + drawbufs[i] = false; bool hasColor = false; for(int i = 0; i < GFXGL->getNumRenderTargets(); ++i) { @@ -200,6 +204,20 @@ void _GFXGLTextureTargetFBOImpl::applyState() glFramebufferTexture2D(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_TEXTURE_2D, 0, 0); } + GLenum *buf = new GLenum[bufsize]; + int count = 0; + for (int i = 0; i < bufsize; i++) + { + if (drawbufs[i]) + { + buf[count] = GL_COLOR_ATTACHMENT0 + i; + count++; + } + } + + glDrawBuffers(bufsize, buf); + + delete[] buf; CHECK_FRAMEBUFFER_STATUS(); } diff --git a/Engine/source/gfx/gl/gfxGLWindowTarget.cpp b/Engine/source/gfx/gl/gfxGLWindowTarget.cpp index c00506835..5f8808cae 100644 --- a/Engine/source/gfx/gl/gfxGLWindowTarget.cpp +++ b/Engine/source/gfx/gl/gfxGLWindowTarget.cpp @@ -57,6 +57,7 @@ void GFXGLWindowTarget::resetMode() _teardownCurrentMode(); _setupNewMode(); } + GFX->beginReset(); } void GFXGLWindowTarget::_onAppSignal(WindowId wnd, S32 event) diff --git a/Engine/source/lighting/advanced/glsl/advancedLightingFeaturesGLSL.cpp b/Engine/source/lighting/advanced/glsl/advancedLightingFeaturesGLSL.cpp index ce90bcc39..c97fbbcf5 100644 --- a/Engine/source/lighting/advanced/glsl/advancedLightingFeaturesGLSL.cpp +++ b/Engine/source/lighting/advanced/glsl/advancedLightingFeaturesGLSL.cpp @@ -156,8 +156,8 @@ void DeferredRTLightingFeatGLSL::processPix( Vector &component oneOverTargetSize->constSortPos = cspPass; } - meta->addStatement( new GenOp( " float id_NL_Att, id_specular;\r\n float3 id_lightcolor;\r\n" ) ); - meta->addStatement( new GenOp( avar( " %s(tex2D(@, @ + float2(0.0, @.y)), id_lightcolor, id_NL_Att, id_specular);\r\n", + meta->addStatement( new GenOp( " float id_NL_Att, id_specular;\r\n vec3 id_lightcolor;\r\n" ) ); + meta->addStatement( new GenOp( avar( " %s(tex2D(@, @ + vec2(0.0, @.y)), id_lightcolor, id_NL_Att, id_specular);\r\n", unconditionLightInfo.c_str() ), lightInfoBuffer, uvScene, oneOverTargetSize ) ); meta->addStatement( new GenOp(" @ = lerp(@, id_lightcolor, 0.5);\r\n", d_lightcolor, d_lightcolor ) ); @@ -167,7 +167,7 @@ void DeferredRTLightingFeatGLSL::processPix( Vector &component // This is kind of weak sauce if( !fd.features[MFT_VertLit] && !fd.features[MFT_ToneMap] && !fd.features[MFT_LightMap] && !fd.features[MFT_SubSurface] ) - meta->addStatement( new GenOp( " @;\r\n", assignColor( new GenOp( "float4(@, 1.0)", d_lightcolor ), Material::Mul ) ) ); + meta->addStatement( new GenOp( " @;\r\n", assignColor( new GenOp( "vec4(@, 1.0)", d_lightcolor ), Material::Mul ) ) ); output = meta; } @@ -311,7 +311,7 @@ void DeferredBumpFeatGLSL::processPix( Vector &componentList, meta->addStatement( new GenOp( " @.xy += @.xy * @;\r\n", bumpNorm, detailBump, detailBumpScale ) ); } - // This var is read from GBufferConditionerHLSL and + // This var is read from GBufferConditionerGLSL and // used in the prepass output. // // By using the 'half' type here we get a bunch of partial @@ -533,11 +533,13 @@ void DeferredPixelSpecularGLSL::processPix( Vector &component specPow->constSortPos = cspPotentialPrimitive; } - Var *specStrength = new Var; - specStrength->setType( "float" ); - specStrength->setName( "specularStrength" ); - specStrength->uniform = true; - specStrength->constSortPos = cspPotentialPrimitive; + Var *specStrength = (Var*)LangElement::find( "specularStrength" ); + if (!specStrength) + { + specStrength = new Var( "specularStrength", "float" ); + specStrength->uniform = true; + specStrength->constSortPos = cspPotentialPrimitive; + } Var *lightInfoSamp = (Var *)LangElement::find( "lightInfoSample" ); Var *d_specular = (Var*)LangElement::find( "d_specular" ); @@ -555,10 +557,10 @@ void DeferredPixelSpecularGLSL::processPix( Vector &component meta->addStatement(new GenOp(" @ = clamp( lerp( @, @ * @, @.a), 0, 1);\r\n", d_specular, d_specular, accuSpecular, d_specular, accuPlc)); } // (a^m)^n = a^(m*n) - meta->addStatement( new GenOp( " @ = pow( abs(@), max((@ / AL_ConstantSpecularPower),1.0f)) * @;\r\n", + meta->addStatement( new GenOp( " @ = pow( abs(@), max((@ / AL_ConstantSpecularPower),1.0f)) * @;\r\n", specDecl, d_specular, specPow, specStrength ) ); - LangElement *specMul = new GenOp( "float4( @.rgb, 0 ) * @", specCol, specular ); + LangElement *specMul = new GenOp( "vec4( @.rgb, 0 ) * @", specCol, specular ); LangElement *final = specMul; // We we have a normal map then mask the specular @@ -682,10 +684,10 @@ void DeferredMinnaertGLSL::processPix( Vector &componentList, Var *d_NL_Att = (Var*)LangElement::find( "d_NL_Att" ); - meta->addStatement( new GenOp( avar( " float4 normalDepth = %s(@, @);\r\n", unconditionPrePassMethod.c_str() ), prepassBuffer, uvScene ) ); + meta->addStatement( new GenOp( avar( " vec4 normalDepth = %s(@, @);\r\n", unconditionPrePassMethod.c_str() ), prepassBuffer, uvScene ) ); meta->addStatement( new GenOp( " float vDotN = dot(normalDepth.xyz, @);\r\n", wsViewVec ) ); meta->addStatement( new GenOp( " float Minnaert = pow( @, @) * pow(vDotN, 1.0 - @);\r\n", d_NL_Att, minnaertConstant, minnaertConstant ) ); - meta->addStatement( new GenOp( " @;\r\n", assignColor( new GenOp( "float4(Minnaert, Minnaert, Minnaert, 1.0)" ), Material::Mul ) ) ); + meta->addStatement( new GenOp( " @;\r\n", assignColor( new GenOp( "vec4(Minnaert, Minnaert, Minnaert, 1.0)" ), Material::Mul ) ) ); output = meta; } @@ -713,7 +715,7 @@ void DeferredSubSurfaceGLSL::processPix( Vector &componentLis MultiLine *meta = new MultiLine; meta->addStatement( new GenOp( " float subLamb = smoothstep(-@.a, 1.0, @) - smoothstep(0.0, 1.0, @);\r\n", subSurfaceParams, d_NL_Att, d_NL_Att ) ); meta->addStatement( new GenOp( " subLamb = max(0.0, subLamb);\r\n" ) ); - meta->addStatement( new GenOp( " @;\r\n", assignColor( new GenOp( "float4(@ + (subLamb * @.rgb), 1.0)", d_lightcolor, subSurfaceParams ), Material::Mul ) ) ); + meta->addStatement( new GenOp( " @;\r\n", assignColor( new GenOp( "vec4(@ + (subLamb * @.rgb), 1.0)", d_lightcolor, subSurfaceParams ), Material::Mul ) ) ); output = meta; } diff --git a/Engine/source/lighting/advanced/glsl/gBufferConditionerGLSL.cpp b/Engine/source/lighting/advanced/glsl/gBufferConditionerGLSL.cpp index 6e5795e83..d2d383148 100644 --- a/Engine/source/lighting/advanced/glsl/gBufferConditionerGLSL.cpp +++ b/Engine/source/lighting/advanced/glsl/gBufferConditionerGLSL.cpp @@ -170,7 +170,7 @@ void GBufferConditionerGLSL::processPix( Vector &componentLis if ( fd.features[ MFT_IsTranslucentZWrite ] ) { alphaVal = new Var( "outAlpha", "float" ); - meta->addStatement( new GenOp( " @ = col.a; // MFT_IsTranslucentZWrite\r\n", new DecOp( alphaVal ) ) ); + meta->addStatement( new GenOp( " @ = OUT_col1.a; // MFT_IsTranslucentZWrite\r\n", new DecOp( alphaVal ) ) ); } // If using interlaced normals, invert the normal @@ -397,7 +397,7 @@ Var* GBufferConditionerGLSL::_unconditionInput( Var *conditionedInput, MultiLine // Recover depth from encoding if(mNormalStorageType != CartesianXYZ) { - const U64 maxValPerChannel = 1 << mBitsPerChannel; + const U64 maxValPerChannel = (U64)1 << mBitsPerChannel; meta->addStatement( new GenOp( " \r\n // Decode depth\r\n" ) ); meta->addStatement( new GenOp( avar( " @.w = dot( @.zw, float2(1.0, 1.0/%llu.0));\r\n", maxValPerChannel - 1 ), retVar, conditionedInput ) ); diff --git a/Engine/source/lighting/advanced/hlsl/advancedLightingFeaturesHLSL.cpp b/Engine/source/lighting/advanced/hlsl/advancedLightingFeaturesHLSL.cpp index deda7cbae..0bfbed42a 100644 --- a/Engine/source/lighting/advanced/hlsl/advancedLightingFeaturesHLSL.cpp +++ b/Engine/source/lighting/advanced/hlsl/advancedLightingFeaturesHLSL.cpp @@ -227,7 +227,7 @@ void DeferredBumpFeatHLSL::processVert( Vector &componentLis const bool useTexAnim = fd.features[MFT_TexAnim]; // Make sure there are texcoords - if( !fd.features[MFT_Parallax] && !fd.features[MFT_DiffuseMap] ) + if( !fd.features[MFT_Parallax] && !fd.features[MFT_DiffuseMap]) { getOutTexCoord( "texCoord", @@ -532,11 +532,13 @@ void DeferredPixelSpecularHLSL::processPix( Vector &component specPow->constSortPos = cspPotentialPrimitive; } - Var *specStrength = new Var; - specStrength->setType( "float" ); - specStrength->setName( "specularStrength" ); - specStrength->uniform = true; - specStrength->constSortPos = cspPotentialPrimitive; + Var *specStrength = (Var*)LangElement::find( "specularStrength" ); + if (!specStrength) + { + specStrength = new Var( "specularStrength", "float" ); + specStrength->uniform = true; + specStrength->constSortPos = cspPotentialPrimitive; + } Var *lightInfoSamp = (Var *)LangElement::find( "lightInfoSample" ); Var *d_specular = (Var*)LangElement::find( "d_specular" ); @@ -556,7 +558,7 @@ void DeferredPixelSpecularHLSL::processPix( Vector &component // (a^m)^n = a^(m*n) meta->addStatement( new GenOp( " @ = pow( abs(@), max((@ / AL_ConstantSpecularPower),1.0f)) * @;\r\n", - specDecl, d_specular, specPow, specStrength ) ); + specDecl, d_specular, specPow, specStrength)); LangElement *specMul = new GenOp( "float4( @.rgb, 0 ) * @", specCol, specular ); LangElement *final = specMul; diff --git a/Engine/source/lighting/advanced/hlsl/gBufferConditionerHLSL.cpp b/Engine/source/lighting/advanced/hlsl/gBufferConditionerHLSL.cpp index 93f9477e9..6f99d035c 100644 --- a/Engine/source/lighting/advanced/hlsl/gBufferConditionerHLSL.cpp +++ b/Engine/source/lighting/advanced/hlsl/gBufferConditionerHLSL.cpp @@ -170,7 +170,7 @@ void GBufferConditionerHLSL::processPix( Vector &componentLis if ( fd.features[ MFT_IsTranslucentZWrite ] ) { alphaVal = new Var( "outAlpha", "float" ); - meta->addStatement( new GenOp( " @ = OUT.col.a; // MFT_IsTranslucentZWrite\r\n", new DecOp( alphaVal ) ) ); + meta->addStatement( new GenOp( " @ = OUT.col1.a; // MFT_IsTranslucentZWrite\r\n", new DecOp( alphaVal ) ) ); } // If using interlaced normals, invert the normal diff --git a/Engine/source/materials/matTextureTarget.cpp b/Engine/source/materials/matTextureTarget.cpp index ba84142f6..f6770c83b 100644 --- a/Engine/source/materials/matTextureTarget.cpp +++ b/Engine/source/materials/matTextureTarget.cpp @@ -47,9 +47,16 @@ bool NamedTexTarget::registerWithName( const String &name ) } // Make sure the target name isn't empty or already taken. - if ( name.isEmpty() || smTargets.contains( name ) ) + if ( name.isEmpty()) + { + Con::errorf("NamedTexTarget::registerWithName( const String &name ) No name given!"); + return false; + } + if (smTargets.contains( name ) ) + { + Con::errorf("NamedTexTarget::registerWithName( %s ) Already used!", name.c_str()); return false; - + } mName = name; mIsRegistered = true; smTargets.insert( mName, this ); diff --git a/Engine/source/materials/materialDefinition.cpp b/Engine/source/materials/materialDefinition.cpp index 408df976f..883d4b7ab 100644 --- a/Engine/source/materials/materialDefinition.cpp +++ b/Engine/source/materials/materialDefinition.cpp @@ -162,6 +162,9 @@ Material::Material() mSeqFramePerSec[i] = 0.0f; mSeqSegSize[i] = 0.0f; + + // Deferred Shading + mMatInfoFlags[i] = 0.0f; } dMemset(mCellIndex, 0, sizeof(mCellIndex)); @@ -170,6 +173,9 @@ Material::Material() dMemset(mNormalMapAtlas, 0, sizeof(mNormalMapAtlas)); dMemset(mUseAnisotropic, 0, sizeof(mUseAnisotropic)); + // Deferred Shading : Metalness + dMemset(mUseMetalness, 0, sizeof(mUseMetalness)); + mImposterLimits = Point4F::Zero; mDoubleSided = false; @@ -204,6 +210,9 @@ Material::Material() mDirectSoundOcclusion = 1.f; mReverbSoundOcclusion = 1.0; + + // Deferred Shading + mIsSky = false; } void Material::initPersistFields() @@ -289,10 +298,7 @@ void Material::initPersistFields() addField( "useAnisotropic", TypeBool, Offset(mUseAnisotropic, Material), MAX_STAGES, "Use anisotropic filtering for the textures of this stage." ); - - addField("envMap", TypeImageFilename, Offset(mEnvMapFilename, Material), MAX_STAGES, - "The name of an environment map cube map to apply to this material." ); - + addField("vertLit", TypeBool, Offset(mVertLit, Material), MAX_STAGES, "If true the vertex color is used for lighting." ); @@ -379,9 +385,6 @@ void Material::initPersistFields() addProtectedField("bumpTex", TypeImageFilename, Offset(mNormalMapFilename, Material), defaultProtectedSetNotEmptyFn, emptyStringProtectedGetFn, MAX_STAGES, "For backwards compatibility.\n@see normalMap\n"); - addProtectedField("envTex", TypeImageFilename, Offset(mEnvMapFilename, Material), - defaultProtectedSetNotEmptyFn, emptyStringProtectedGetFn, MAX_STAGES, - "For backwards compatibility.\n@see envMap\n"); addProtectedField("colorMultiply", TypeColorF, Offset(mDiffuse, Material), defaultProtectedSetNotEmptyFn, emptyStringProtectedGetFn, MAX_STAGES, "For backwards compatibility.\n@see diffuseColor\n"); @@ -417,6 +420,9 @@ void Material::initPersistFields() addField("dynamicCubemap", TypeBool, Offset(mDynamicCubemap, Material), "Enables the material to use the dynamic cubemap from the ShapeBase object its applied to." ); + addField("isSky", TypeBool, Offset(mIsSky, Material), + "Sky support. Alters draw dimensions." ); + addGroup( "Behavioral" ); addField( "showFootprints", TypeBool, Offset( mShowFootprints, Material ), diff --git a/Engine/source/materials/materialDefinition.h b/Engine/source/materials/materialDefinition.h index 491b56cf0..d676c8f02 100644 --- a/Engine/source/materials/materialDefinition.h +++ b/Engine/source/materials/materialDefinition.h @@ -221,8 +221,6 @@ public: /// The strength scalar for the detail normal map. F32 mDetailNormalMapStrength[MAX_STAGES]; - FileName mEnvMapFilename[MAX_STAGES]; - /// This color is the diffuse color of the material /// or if it has a texture it is multiplied against /// the diffuse texture color. @@ -287,12 +285,18 @@ public: /// If the stage should use anisotropic filtering. bool mUseAnisotropic[MAX_STAGES]; + // Deferred Shading: Metalness + bool mUseMetalness[MAX_STAGES]; + bool mDoubleSided; String mCubemapName; CubemapData* mCubemapData; bool mDynamicCubemap; + // Deferred Shading + bool mIsSky; + F32 mMatInfoFlags[MAX_STAGES]; bool mTranslucent; BlendOp mTranslucentBlendOp; bool mTranslucentZWrite; diff --git a/Engine/source/materials/processedMaterial.cpp b/Engine/source/materials/processedMaterial.cpp index 10181179e..00a3b8ec2 100644 --- a/Engine/source/materials/processedMaterial.cpp +++ b/Engine/source/materials/processedMaterial.cpp @@ -455,14 +455,6 @@ void ProcessedMaterial::_setStageData() if(!mStages[i].getTex( MFT_SpecularMap )) mMaterial->logError("Failed to load specular map %s for stage %i", _getTexturePath(mMaterial->mSpecularMapFilename[i]).c_str(), i); } - - // EnironmentMap - if( mMaterial->mEnvMapFilename[i].isNotEmpty() ) - { - mStages[i].setTex( MFT_EnvMap, _createTexture( mMaterial->mEnvMapFilename[i], &GFXDefaultStaticDiffuseProfile ) ); - if(!mStages[i].getTex( MFT_EnvMap )) - mMaterial->logError("Failed to load environment map %s for stage %i", _getTexturePath(mMaterial->mEnvMapFilename[i]).c_str(), i); - } } mMaterial->mCubemapData = dynamic_cast(Sim::findObject( mMaterial->mCubemapName )); diff --git a/Engine/source/materials/processedShaderMaterial.cpp b/Engine/source/materials/processedShaderMaterial.cpp index 431e3bce6..37f745253 100644 --- a/Engine/source/materials/processedShaderMaterial.cpp +++ b/Engine/source/materials/processedShaderMaterial.cpp @@ -106,6 +106,9 @@ void ShaderConstHandles::init( GFXShader *shader, CustomMaterial* mat /*=NULL*/ for (S32 i = 0; i < Material::MAX_TEX_PER_PASS; ++i) mTexHandlesSC[i] = shader->getShaderConstHandle(mat->mSamplerNames[i]); } + + // Deferred Shading + mMatInfoFlagsSC = shader->getShaderConstHandle(ShaderGenVars::matInfoFlags); } /// @@ -208,28 +211,6 @@ bool ProcessedShaderMaterial::init( const FeatureSet &features, mInstancingState = new InstancingState(); mInstancingState->setFormat( &_getRPD( 0 )->shader->mInstancingFormat, mVertexFormat ); } - - // Check for a RenderTexTargetBin assignment - // *IMPORTANT NOTE* - // This is a temporary solution for getting diffuse mapping working with tex targets for standard materials - // It should be removed once this is done properly, at that time the sAllowTextureTargetAssignment should also be removed - // from Material (it is necessary for catching shadow maps/post effect this shouldn't be applied to) - if (Material::sAllowTextureTargetAssignment) - if (mMaterial && mMaterial->mDiffuseMapFilename[0].isNotEmpty() && mMaterial->mDiffuseMapFilename[0].substr( 0, 1 ).equal("#")) - { - String texTargetBufferName = mMaterial->mDiffuseMapFilename[0].substr(1, mMaterial->mDiffuseMapFilename[0].length() - 1); - NamedTexTarget *texTarget = NamedTexTarget::find( texTargetBufferName ); - - RenderPassData* rpd = getPass(0); - - if (rpd) - { - rpd->mTexSlot[0].texTarget = texTarget; - rpd->mTexType[0] = Material::TexTarget; - rpd->mSamplerNames[0] = "diffuseMap"; - } - } - return true; } @@ -353,11 +334,18 @@ void ProcessedShaderMaterial::_determineFeatures( U32 stageNum, fd.features.addFeature( MFT_VertLit ); // cubemaps only available on stage 0 for now - bramage - if ( stageNum < 1 && + if ( stageNum < 1 && mMaterial->isTranslucent() && ( ( mMaterial->mCubemapData && mMaterial->mCubemapData->mCubemap ) || mMaterial->mDynamicCubemap ) ) - fd.features.addFeature( MFT_CubeMap ); + { + fd.features.addFeature( MFT_CubeMap ); + } + if (mMaterial->mIsSky) + { + fd.features.addFeature(MFT_CubeMap); + fd.features.addFeature(MFT_SkyBox); + } fd.features.addFeature( MFT_Visibility ); if ( lastStage && @@ -428,7 +416,6 @@ void ProcessedShaderMaterial::_determineFeatures( U32 stageNum, if ( mMaterial->mAccuEnabled[stageNum] ) { - fd.features.addFeature( MFT_AccuMap ); mHasAccumulation = true; } @@ -441,19 +428,7 @@ void ProcessedShaderMaterial::_determineFeatures( U32 stageNum, fd.features.removeFeature( MFT_AccuMap ); mHasAccumulation = false; } - - // if we still have the AccuMap feature, we add all accu constant features - if ( fd.features[ MFT_AccuMap ] ) { - // add the dependencies of the accu map - fd.features.addFeature( MFT_AccuScale ); - fd.features.addFeature( MFT_AccuDirection ); - fd.features.addFeature( MFT_AccuStrength ); - fd.features.addFeature( MFT_AccuCoverage ); - fd.features.addFeature( MFT_AccuSpecular ); - // now remove some features that are not compatible with this - fd.features.removeFeature( MFT_UseInstancing ); - } - + // Without a base texture use the diffuse color // feature to ensure some sort of output. if (!fd.features[MFT_DiffuseMap]) @@ -1175,7 +1150,12 @@ void ProcessedShaderMaterial::_setShaderConstants(SceneRenderState * state, cons 0.0f, 0.0f ); // TODO: Wrap mode flags? shaderConsts->setSafe(handles->mBumpAtlasTileSC, atlasTileParams); } - + + // Deferred Shading: Determine Material Info Flags + S32 matInfoFlags = + (mMaterial->mEmissive[stageNum] ? 1 : 0); + mMaterial->mMatInfoFlags[stageNum] = matInfoFlags / 255.0f; + shaderConsts->setSafe(handles->mMatInfoFlagsSC, mMaterial->mMatInfoFlags[stageNum]); if( handles->mAccuScaleSC->isValid() ) shaderConsts->set( handles->mAccuScaleSC, mMaterial->mAccuScale[stageNum] ); if( handles->mAccuDirectionSC->isValid() ) diff --git a/Engine/source/materials/processedShaderMaterial.h b/Engine/source/materials/processedShaderMaterial.h index dc5dbf872..9bcc0eec7 100644 --- a/Engine/source/materials/processedShaderMaterial.h +++ b/Engine/source/materials/processedShaderMaterial.h @@ -87,6 +87,9 @@ public: GFXShaderConstHandle *mImposterUVs; GFXShaderConstHandle *mImposterLimits; + // Deferred Shading : Material Info Flags + GFXShaderConstHandle* mMatInfoFlagsSC; + GFXShaderConstHandle* mTexHandlesSC[Material::MAX_TEX_PER_PASS]; GFXShaderConstHandle* mRTParamsSC[TEXTURE_STAGE_COUNT]; diff --git a/Engine/source/renderInstance/renderBinManager.cpp b/Engine/source/renderInstance/renderBinManager.cpp index 8ebe69334..c3b3f135d 100644 --- a/Engine/source/renderInstance/renderBinManager.cpp +++ b/Engine/source/renderInstance/renderBinManager.cpp @@ -36,7 +36,8 @@ RenderBinManager::RenderBinManager( const RenderInstType& ritype, F32 renderOrde mRenderInstType( ritype ), mRenderOrder( renderOrder ), mProcessAddOrder( processAddOrder ), - mRenderPass( NULL ) + mRenderPass( NULL ), + mBasicOnly ( false ) { VECTOR_SET_ASSOCIATION( mElementList ); mElementList.reserve( 2048 ); @@ -60,6 +61,9 @@ void RenderBinManager::initPersistFields() addField("processAddOrder", TypeF32, Offset(mProcessAddOrder, RenderBinManager), "Defines the order for adding instances in relation to other bins." ); + addField( "basicOnly", TypeBool, Offset(mBasicOnly, RenderBinManager), + "Limites the render bin to basic lighting only." ); + Parent::initPersistFields(); } diff --git a/Engine/source/renderInstance/renderBinManager.h b/Engine/source/renderInstance/renderBinManager.h index cc5bbe6c3..2a1a4cbb5 100644 --- a/Engine/source/renderInstance/renderBinManager.h +++ b/Engine/source/renderInstance/renderBinManager.h @@ -128,6 +128,8 @@ protected: /// RenderInst if available, otherwise, return NULL. inline BaseMatInstance* getMaterial( RenderInst *inst ) const; + // Limits bin to rendering in basic lighting only. + bool mBasicOnly; }; @@ -160,6 +162,7 @@ inline BaseMatInstance* RenderBinManager::getMaterial( RenderInst *inst ) const { if ( inst->type == RenderPassManager::RIT_Mesh || inst->type == RenderPassManager::RIT_Decal || + inst->type == RenderPassManager::RIT_DecalRoad || inst->type == RenderPassManager::RIT_Translucent ) return static_cast(inst)->matInst; diff --git a/Engine/source/renderInstance/renderGlowMgr.cpp b/Engine/source/renderInstance/renderGlowMgr.cpp index ed48c095f..14e6ce016 100644 --- a/Engine/source/renderInstance/renderGlowMgr.cpp +++ b/Engine/source/renderInstance/renderGlowMgr.cpp @@ -79,6 +79,7 @@ void RenderGlowMgr::GlowMaterialHook::_overrideFeatures( ProcessedMaterial *mat, // the glow materials. fd.features.removeFeature( MFT_Fog ); fd.features.removeFeature( MFT_HDROut ); + fd.features.addFeature( MFT_Imposter ); } RenderGlowMgr::RenderGlowMgr() @@ -89,6 +90,7 @@ RenderGlowMgr::RenderGlowMgr() Point2I( 512, 512 ) ) { notifyType( RenderPassManager::RIT_Decal ); + notifyType( RenderPassManager::RIT_DecalRoad ); notifyType( RenderPassManager::RIT_Translucent ); notifyType( RenderPassManager::RIT_Particle ); diff --git a/Engine/source/renderInstance/renderMeshMgr.cpp b/Engine/source/renderInstance/renderMeshMgr.cpp index 67114329d..b224e5469 100644 --- a/Engine/source/renderInstance/renderMeshMgr.cpp +++ b/Engine/source/renderInstance/renderMeshMgr.cpp @@ -144,6 +144,14 @@ void RenderMeshMgr::render(SceneRenderState * state) if( !mat ) mat = MATMGR->getWarningMatInstance(); + // Check if bin is disabled in advanced lighting. + // Allow forward rendering pass on custom materials. + + if ( ( MATMGR->getPrePassEnabled() && mBasicOnly && !mat->isCustomMaterial() ) ) + { + j++; + continue; + } U32 matListEnd = j; lastMiscTex = sgData.miscTex; diff --git a/Engine/source/renderInstance/renderObjectMgr.cpp b/Engine/source/renderInstance/renderObjectMgr.cpp index 6f79f9128..6cfab0aba 100644 --- a/Engine/source/renderInstance/renderObjectMgr.cpp +++ b/Engine/source/renderInstance/renderObjectMgr.cpp @@ -22,6 +22,8 @@ #include "renderObjectMgr.h" #include "console/consoleTypes.h" #include "scene/sceneObject.h" +#include "materials/materialManager.h" +#include "scene/sceneRenderState.h" IMPLEMENT_CONOBJECT(RenderObjectMgr); @@ -66,6 +68,10 @@ void RenderObjectMgr::render( SceneRenderState *state ) if(!mElementList.size()) return; + // Check if bin is disabled in advanced lighting. + if ( MATMGR->getPrePassEnabled() && mBasicOnly ) + return; + for( U32 i=0; i(mElementList[i].inst); diff --git a/Engine/source/renderInstance/renderPassManager.cpp b/Engine/source/renderInstance/renderPassManager.cpp index 68daed77e..f620a627b 100644 --- a/Engine/source/renderInstance/renderPassManager.cpp +++ b/Engine/source/renderInstance/renderPassManager.cpp @@ -51,6 +51,7 @@ const RenderInstType RenderPassManager::RIT_Terrain("Terrain"); const RenderInstType RenderPassManager::RIT_Object("Object"); const RenderInstType RenderPassManager::RIT_ObjectTranslucent("ObjectTranslucent"); const RenderInstType RenderPassManager::RIT_Decal("Decal"); +const RenderInstType RenderPassManager::RIT_DecalRoad("DecalRoad"); const RenderInstType RenderPassManager::RIT_Water("Water"); const RenderInstType RenderPassManager::RIT_Foliage("Foliage"); const RenderInstType RenderPassManager::RIT_VolumetricFog("ObjectVolumetricFog"); diff --git a/Engine/source/renderInstance/renderPassManager.h b/Engine/source/renderInstance/renderPassManager.h index b192fdb0e..2aa1e37ee 100644 --- a/Engine/source/renderInstance/renderPassManager.h +++ b/Engine/source/renderInstance/renderPassManager.h @@ -108,6 +108,7 @@ public: static const RenderInstType RIT_Object; // objects that do their own rendering static const RenderInstType RIT_ObjectTranslucent;// self rendering; but sorted with static const RenderInstType RIT_Translucent static const RenderInstType RIT_Decal; + static const RenderInstType RIT_DecalRoad; static const RenderInstType RIT_Water; static const RenderInstType RIT_Foliage; static const RenderInstType RIT_VolumetricFog; diff --git a/Engine/source/renderInstance/renderTerrainMgr.cpp b/Engine/source/renderInstance/renderTerrainMgr.cpp index f298ff51c..4a67e686f 100644 --- a/Engine/source/renderInstance/renderTerrainMgr.cpp +++ b/Engine/source/renderInstance/renderTerrainMgr.cpp @@ -35,6 +35,7 @@ #include "terrain/terrCell.h" #include "terrain/terrCellMaterial.h" #include "math/util/matrixSet.h" +#include "materials/materialManager.h" bool RenderTerrainMgr::smRenderWireframe = false; @@ -117,6 +118,10 @@ void RenderTerrainMgr::render( SceneRenderState *state ) if ( mInstVector.empty() ) return; + // Check if bin is disabled in advanced lighting. + if ( MATMGR->getPrePassEnabled() && mBasicOnly ) + return; + PROFILE_SCOPE( RenderTerrainMgr_Render ); GFXTransformSaver saver; diff --git a/Engine/source/scene/reflectionManager.cpp b/Engine/source/scene/reflectionManager.cpp index 057c72c0a..323e11c8a 100644 --- a/Engine/source/scene/reflectionManager.cpp +++ b/Engine/source/scene/reflectionManager.cpp @@ -58,7 +58,7 @@ MODULE_END; GFX_ImplementTextureProfile( ReflectRenderTargetProfile, GFXTextureProfile::DiffuseMap, - GFXTextureProfile::PreserveSize | GFXTextureProfile::NoMipmap | GFXTextureProfile::RenderTarget | GFXTextureProfile::Pooled, + GFXTextureProfile::PreserveSize | GFXTextureProfile::RenderTarget | GFXTextureProfile::Pooled, GFXTextureProfile::NONE ); GFX_ImplementTextureProfile( RefractTextureProfile, diff --git a/Engine/source/scene/reflectionMatHook.cpp b/Engine/source/scene/reflectionMatHook.cpp index b869cabc9..646b0145c 100644 --- a/Engine/source/scene/reflectionMatHook.cpp +++ b/Engine/source/scene/reflectionMatHook.cpp @@ -89,8 +89,6 @@ void ReflectionMaterialHook::_overrideFeatures( ProcessedMaterial *mat, return; } - // Forward shading on materials in reflections - fd.features.addFeature( MFT_ForwardShading ); fd.features.addFeature( MFT_Fog ); } diff --git a/Engine/source/scene/reflector.cpp b/Engine/source/scene/reflector.cpp index 254839813..1addaf191 100644 --- a/Engine/source/scene/reflector.cpp +++ b/Engine/source/scene/reflector.cpp @@ -419,7 +419,6 @@ void CubeReflector::updateFace( const ReflectParams ¶ms, U32 faceidx ) reflectRenderState.getMaterialDelegate().bind( REFLECTMGR, &ReflectionManager::getReflectionMaterial ); reflectRenderState.setDiffuseCameraTransform( params.query->cameraMatrix ); - reflectRenderState.disableAdvancedLightingBins(true); // render scene LIGHTMGR->registerGlobalLights( &reflectRenderState.getCullingFrustum(), false ); @@ -633,7 +632,6 @@ void PlaneReflector::updateReflection( const ReflectParams ¶ms ) renderStateLeft.setSceneRenderField(0); renderStateLeft.getMaterialDelegate().bind( REFLECTMGR, &ReflectionManager::getReflectionMaterial ); renderStateLeft.setDiffuseCameraTransform( params.query->eyeTransforms[0] ); - renderStateLeft.disableAdvancedLightingBins(true); gClientSceneGraph->renderSceneNoLights( &renderStateLeft, objTypeFlag ); @@ -672,7 +670,6 @@ void PlaneReflector::updateReflection( const ReflectParams ¶ms ) reflectRenderState.getMaterialDelegate().bind( REFLECTMGR, &ReflectionManager::getReflectionMaterial ); reflectRenderState.setDiffuseCameraTransform( params.query->cameraMatrix ); - reflectRenderState.disableAdvancedLightingBins(true); gClientSceneGraph->renderSceneNoLights( &reflectRenderState, objTypeFlag ); } diff --git a/Engine/source/shaderGen/GLSL/accuFeatureGLSL.cpp b/Engine/source/shaderGen/GLSL/accuFeatureGLSL.cpp index 04a280e09..aa2eda41d 100644 --- a/Engine/source/shaderGen/GLSL/accuFeatureGLSL.cpp +++ b/Engine/source/shaderGen/GLSL/accuFeatureGLSL.cpp @@ -56,7 +56,7 @@ void AccuTexFeatGLSL::processPix(Vector &componentList, output = meta; // OUT.col - Var *color = (Var*) LangElement::find( "col" ); + Var *color = (Var*) LangElement::find( "col1" ); if (!color) { output = new GenOp(" //NULL COLOR!"); @@ -144,8 +144,6 @@ void AccuTexFeatGLSL::processPix(Vector &componentList, // get the accu pixel color meta->addStatement( new GenOp( " @ = tex2D(@, @ * @);\r\n", colorAccuDecl, accuMap, inTex, accuScale ) ); - if (!fd.features[MFT_Imposter]) - meta->addStatement(new GenOp(" @ = toLinear(@);\r\n", accuColor, accuColor)); // scale up normals meta->addStatement( new GenOp( " @.xyz = @.xyz * 2.0 - 0.5;\r\n", bumpNorm, bumpNorm ) ); diff --git a/Engine/source/shaderGen/HLSL/accuFeatureHLSL.cpp b/Engine/source/shaderGen/HLSL/accuFeatureHLSL.cpp index 1e90faa12..2bafc06f4 100644 --- a/Engine/source/shaderGen/HLSL/accuFeatureHLSL.cpp +++ b/Engine/source/shaderGen/HLSL/accuFeatureHLSL.cpp @@ -56,7 +56,7 @@ void AccuTexFeatHLSL::processPix( Vector &componentList, output = meta; // OUT.col - Var *color = (Var*) LangElement::find( "col" ); + Var *color = (Var*) LangElement::find( "col1" ); if (!color) { output = new GenOp(" //NULL COLOR!"); @@ -141,8 +141,6 @@ void AccuTexFeatHLSL::processPix( Vector &componentList, // get the accu pixel color meta->addStatement( new GenOp( " @ = tex2D(@, @ * @);\r\n", colorAccuDecl, accuMap, inTex, accuScale ) ); - if (!fd.features[MFT_Imposter]) - meta->addStatement(new GenOp(" @ = toLinear(@);\r\n", accuColor, accuColor)); // scale up normals meta->addStatement( new GenOp( " @.xyz = @.xyz * 2.0 - 0.5;\r\n", bumpNorm, bumpNorm ) ); diff --git a/Templates/Empty/game/shaders/common/postFx/hdr/gl/finalPassCombineP.glsl b/Templates/Empty/game/shaders/common/postFx/hdr/gl/finalPassCombineP.glsl index 38762baa5..f34cff1ef 100644 --- a/Templates/Empty/game/shaders/common/postFx/hdr/gl/finalPassCombineP.glsl +++ b/Templates/Empty/game/shaders/common/postFx/hdr/gl/finalPassCombineP.glsl @@ -42,10 +42,11 @@ uniform vec3 g_fBlueShiftColor; uniform float g_fBloomScale; uniform float g_fOneOverGamma; +uniform float Brightness; +uniform float Contrast; out vec4 OUT_col; - void main() { vec4 _sample = hdrDecode( texture( sceneTex, IN_uv0 ) ); @@ -94,5 +95,11 @@ void main() // Apply gamma correction _sample.rgb = pow( abs(_sample.rgb), vec3(g_fOneOverGamma) ); + // Apply contrast + _sample.rgb = ((_sample.rgb - 0.5f) * Contrast) + 0.5f; + + // Apply brightness + _sample.rgb += Brightness; + OUT_col = _sample; } diff --git a/Templates/Full/game/art/prefabs/fire.prefab b/Templates/Full/game/art/prefabs/fire.prefab new file mode 100644 index 000000000..6213e33a8 --- /dev/null +++ b/Templates/Full/game/art/prefabs/fire.prefab @@ -0,0 +1,49 @@ +//--- OBJECT WRITE BEGIN --- +$ThisPrefab = new SimGroup() { + canSave = "1"; + canSaveDynamicFields = "1"; + + new ParticleEmitterNode() { + active = "1"; + emitter = "FireEmitter"; + velocity = "1"; + dataBlock = "EmberNode"; + position = "0 0 0"; + rotation = "1 0 0 0"; + scale = "1 1 1"; + canSave = "1"; + canSaveDynamicFields = "1"; + }; + new PointLight() { + radius = "10"; + isEnabled = "1"; + color = "1 0.603922 0 1"; + brightness = "1"; + castShadows = "1"; + staticRefreshFreq = "250"; + dynamicRefreshFreq = "8"; + priority = "1"; + animate = "1"; + animationPeriod = "1"; + animationPhase = "1"; + flareScale = "1"; + attenuationRatio = "0 1 1"; + shadowType = "DualParaboloidSinglePass"; + texSize = "512"; + overDarkFactor = "2000 1000 500 100"; + shadowDistance = "400"; + shadowSoftness = "0.15"; + numSplits = "1"; + logWeight = "0.91"; + fadeStartDistance = "0"; + lastSplitTerrainOnly = "0"; + representedInLightmap = "0"; + shadowDarkenColor = "0 0 0 -1"; + includeLightmappedGeometryInShadow = "0"; + position = "0 0 0"; + rotation = "1 0 0 0"; + canSave = "1"; + canSaveDynamicFields = "1"; + }; +}; +//--- OBJECT WRITE END --- diff --git a/Templates/Full/game/art/shapes/trees/defaulttree/materials.cs b/Templates/Full/game/art/shapes/trees/defaulttree/materials.cs index f82946ccb..c17ea71eb 100644 --- a/Templates/Full/game/art/shapes/trees/defaulttree/materials.cs +++ b/Templates/Full/game/art/shapes/trees/defaulttree/materials.cs @@ -27,7 +27,6 @@ singleton Material(defaultTree_bark_material) diffuseMap[0] = "art/shapes/trees/defaulttree/defaulttree_bark_diffuse.dds"; normalMap[0] = "art/shapes/trees/defaulttree/defaulttree_bark_normal_specular.dds"; - specularMap[0] = ""; diffuseColor[0] = "1 1 1 1"; specular[0] = "0.9 0.9 0.9 1"; @@ -37,6 +36,7 @@ singleton Material(defaultTree_bark_material) translucent = false; translucentBlendOp = "None"; pixelSpecular[0] = "1"; + castDynamicShadows = "0"; }; singleton Material(defaulttree_material) @@ -45,7 +45,6 @@ singleton Material(defaulttree_material) diffuseMap[0] = "art/shapes/trees/defaulttree/defaulttree_diffuse_transparency.dds"; normalMap[0] = "art/shapes/trees/defaulttree/defaulttree_normal_specular.dds"; - specularMap[0] = ""; diffuseColor[0] = "1 1 1 1"; specular[0] = "0.9 0.9 0.9 1"; @@ -57,6 +56,7 @@ singleton Material(defaulttree_material) pixelSpecular[0] = "1"; alphaTest = "1"; alphaRef = "127"; + castDynamicShadows = "0"; }; singleton Material(defaultTree_fronds_material) @@ -71,6 +71,7 @@ singleton Material(defaultTree_fronds_material) alphaTest = "1"; alphaRef = "114"; translucent = "1"; + castDynamicShadows = "0"; }; singleton Material(defaulttree_ColorEffectR27G177B88_material) @@ -79,4 +80,6 @@ singleton Material(defaulttree_ColorEffectR27G177B88_material) diffuseColor[0] = "0.105882 0.694118 0.345098 1"; specularPower[0] = "10"; translucentBlendOp = "None"; + castDynamicShadows = "0"; + castShadows = "0"; }; diff --git a/Templates/Full/game/art/skies/Desert_Sky/materials.cs b/Templates/Full/game/art/skies/Desert_Sky/materials.cs index af6a979c8..0ee4080d8 100644 --- a/Templates/Full/game/art/skies/Desert_Sky/materials.cs +++ b/Templates/Full/game/art/skies/Desert_Sky/materials.cs @@ -34,4 +34,5 @@ singleton Material( DesertSkyMat ) { cubemap = DesertSkyCubemap; materialTag0 = "Skies"; + isSky = true; }; diff --git a/Templates/Full/game/art/skies/night/materials.cs b/Templates/Full/game/art/skies/night/materials.cs index 79cc050fc..11f9f6348 100644 --- a/Templates/Full/game/art/skies/night/materials.cs +++ b/Templates/Full/game/art/skies/night/materials.cs @@ -34,6 +34,7 @@ singleton Material( NightSkyMat ) { cubemap = NightCubemap; materialTag0 = "Skies"; + isSky = true; }; singleton Material( Moon_Glow_Mat ) @@ -50,4 +51,5 @@ singleton Material( Moon_Mat ) emissive = true; translucent = true; vertColor[ 0 ] = true; + isSky = true; }; diff --git a/Templates/Full/game/core/art/skies/blank/materials.cs b/Templates/Full/game/core/art/skies/blank/materials.cs index 179eafcf2..39f268866 100644 --- a/Templates/Full/game/core/art/skies/blank/materials.cs +++ b/Templates/Full/game/core/art/skies/blank/materials.cs @@ -34,6 +34,7 @@ singleton Material( BlackSkyMat ) { cubemap = BlackSkyCubemap; materialTag0 = "Skies"; + isSky = true; }; singleton CubemapData( BlueSkyCubemap ) @@ -50,6 +51,7 @@ singleton Material( BlueSkyMat ) { cubemap = BlueSkyCubemap; materialTag0 = "Skies"; + isSky = true; }; singleton CubemapData( GreySkyCubemap ) @@ -66,4 +68,5 @@ singleton Material( GreySkyMat ) { cubemap = GreySkyCubemap; materialTag0 = "Skies"; + isSky = true; }; diff --git a/Templates/Full/game/core/scripts/client/lighting/advanced/init.cs b/Templates/Full/game/core/scripts/client/lighting/advanced/init.cs index 2c78e9ca4..d74aff69a 100644 --- a/Templates/Full/game/core/scripts/client/lighting/advanced/init.cs +++ b/Templates/Full/game/core/scripts/client/lighting/advanced/init.cs @@ -43,6 +43,7 @@ exec( "./shaders.cs" ); exec( "./lightViz.cs" ); exec( "./shadowViz.cs" ); exec( "./shadowViz.gui" ); +exec( "./deferredShading.cs" ); function onActivateAdvancedLM() { @@ -58,12 +59,18 @@ function onActivateAdvancedLM() // Enable the offscreen target so that AL will work // with MSAA back buffers and for HDR rendering. AL_FormatToken.enable(); + + // Activate Deferred Shading + AL_DeferredShading.enable(); } function onDeactivateAdvancedLM() { // Disable the offscreen render target. AL_FormatToken.disable(); + + // Deactivate Deferred Shading + AL_DeferredShading.disable(); } function setAdvancedLighting() diff --git a/Templates/Full/game/core/scripts/client/postFx/caustics.cs b/Templates/Full/game/core/scripts/client/postFx/caustics.cs index 3e8b14de0..11fda8083 100644 --- a/Templates/Full/game/core/scripts/client/postFx/caustics.cs +++ b/Templates/Full/game/core/scripts/client/postFx/caustics.cs @@ -51,7 +51,7 @@ singleton ShaderData( PFX_CausticsShader ) singleton PostEffect( CausticsPFX ) { isEnabled = false; - renderTime = "PFXBeforeBin"; + renderTime = "PFXAfterDiffuse"; renderBin = "ObjTranslucentBin"; //renderPriority = 0.1; diff --git a/Templates/Full/game/core/scripts/client/postFx/hdr.cs b/Templates/Full/game/core/scripts/client/postFx/hdr.cs index 136a5ca95..6c8e870d0 100644 --- a/Templates/Full/game/core/scripts/client/postFx/hdr.cs +++ b/Templates/Full/game/core/scripts/client/postFx/hdr.cs @@ -172,6 +172,8 @@ singleton ShaderData( HDR_CombineShader ) samplerNames[2] = "$bloomTex"; samplerNames[3] = "$colorCorrectionTex"; + samplerNames[4] = "prepassTex"; + pixVersion = 3.0; }; @@ -469,6 +471,7 @@ singleton PostEffect( HDRPostFX ) texture[1] = "#adaptedLum"; texture[2] = "#bloomFinal"; texture[3] = $HDRPostFX::colorCorrectionRamp; + texture[4] = "#prepass"; target = "$backBuffer"; }; }; diff --git a/Templates/Full/game/core/scripts/client/postFx/turbulence.cs b/Templates/Full/game/core/scripts/client/postFx/turbulence.cs index c2309f808..dd8c0e2dc 100644 --- a/Templates/Full/game/core/scripts/client/postFx/turbulence.cs +++ b/Templates/Full/game/core/scripts/client/postFx/turbulence.cs @@ -47,7 +47,7 @@ singleton PostEffect( TurbulenceFx ) isEnabled = false; allowReflectPass = true; - renderTime = "PFXAfterBin"; + renderTime = "PFXAfterDiffuse"; renderBin = "GlowBin"; renderPriority = 0.5; // Render after the glows themselves diff --git a/Templates/Full/game/core/scripts/client/scatterSky.cs b/Templates/Full/game/core/scripts/client/scatterSky.cs index 7701b0adf..a2b19ab23 100644 --- a/Templates/Full/game/core/scripts/client/scatterSky.cs +++ b/Templates/Full/game/core/scripts/client/scatterSky.cs @@ -22,13 +22,13 @@ new GFXStateBlockData( ScatterSkySBData ) { - cullDefined = true; + //cullDefined = true; cullMode = "GFXCullNone"; zDefined = true; zEnable = true; zWriteEnable = false; - zFunc = "GFXCmpLessEqual"; + //zFunc = "GFXCmpLessEqual"; samplersDefined = true; samplerStates[0] = SamplerClampLinear; diff --git a/Templates/Full/game/shaders/common/basicCloudsV.hlsl b/Templates/Full/game/shaders/common/basicCloudsV.hlsl index 49842fd37..a3d4bb5fe 100644 --- a/Templates/Full/game/shaders/common/basicCloudsV.hlsl +++ b/Templates/Full/game/shaders/common/basicCloudsV.hlsl @@ -46,6 +46,7 @@ ConnectData main( CloudVert IN ) ConnectData OUT; OUT.hpos = mul(modelview, IN.pos); + OUT.hpos.w = OUT.hpos.z; float2 uv = IN.uv0; uv += texOffset; diff --git a/Templates/Full/game/shaders/common/cloudLayerV.hlsl b/Templates/Full/game/shaders/common/cloudLayerV.hlsl index 8c1cc555f..c34a57c05 100644 --- a/Templates/Full/game/shaders/common/cloudLayerV.hlsl +++ b/Templates/Full/game/shaders/common/cloudLayerV.hlsl @@ -60,8 +60,8 @@ uniform float3 texScale; ConnectData main( CloudVert IN ) { ConnectData OUT; - OUT.hpos = mul(modelview, IN.pos); + OUT.hpos.w = OUT.hpos.z; // Offset the uv so we don't have a seam directly over our head. float2 uv = IN.uv0 + float2( 0.5, 0.5 ); diff --git a/Templates/Full/game/shaders/common/gl/basicCloudsV.glsl b/Templates/Full/game/shaders/common/gl/basicCloudsV.glsl index cccbafa8c..40c597120 100644 --- a/Templates/Full/game/shaders/common/gl/basicCloudsV.glsl +++ b/Templates/Full/game/shaders/common/gl/basicCloudsV.glsl @@ -41,6 +41,7 @@ out vec2 texCoord; void main() { gl_Position = tMul(modelview, IN_pos); + gl_Position.w = gl_Position.z; vec2 uv = IN_uv0; uv += texOffset; diff --git a/Templates/Full/game/shaders/common/gl/cloudLayerV.glsl b/Templates/Full/game/shaders/common/gl/cloudLayerV.glsl index 395c6f286..cba5c009a 100644 --- a/Templates/Full/game/shaders/common/gl/cloudLayerV.glsl +++ b/Templates/Full/game/shaders/common/gl/cloudLayerV.glsl @@ -62,6 +62,7 @@ void main() vec2 IN_uv0 = vTexCoord0.st; gl_Position = modelview * IN_pos; + gl_Position.w = gl_Position.z; // Offset the uv so we don't have a seam directly over our head. vec2 uv = IN_uv0 + vec2( 0.5, 0.5 ); diff --git a/Templates/Full/game/shaders/common/gl/scatterSkyP.glsl b/Templates/Full/game/shaders/common/gl/scatterSkyP.glsl index d9fa80bcf..b4e70f902 100644 --- a/Templates/Full/game/shaders/common/gl/scatterSkyP.glsl +++ b/Templates/Full/game/shaders/common/gl/scatterSkyP.glsl @@ -73,5 +73,8 @@ void main() discard; OUT_col.a = 1; + + OUT_col = clamp(OUT_col, 0.0, 1.0); + OUT_col = hdrEncode( OUT_col ); } diff --git a/Templates/Full/game/shaders/common/lighting/advanced/deferredColorShaderP.hlsl b/Templates/Full/game/shaders/common/lighting/advanced/deferredColorShaderP.hlsl new file mode 100644 index 000000000..5e6d0a984 --- /dev/null +++ b/Templates/Full/game/shaders/common/lighting/advanced/deferredColorShaderP.hlsl @@ -0,0 +1,44 @@ +//----------------------------------------------------------------------------- +// Copyright (c) 2012 GarageGames, LLC +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. +//----------------------------------------------------------------------------- + +struct Fragout +{ + float4 col : COLOR0; + float4 col1 : COLOR1; + float4 col2 : COLOR2; +}; + +//----------------------------------------------------------------------------- +// Main +//----------------------------------------------------------------------------- +Fragout main( ) +{ + Fragout OUT; + + OUT.col = float4(0.0, 0.0, 0.0, 0.0); + OUT.col1 = float4(1.0, 1.0, 1.0, 1.0); + + // Draw on color buffer. + OUT.col2 = float4(1.0, 0.0, 0.0, 1.0); + + return OUT; +} diff --git a/Templates/Full/game/shaders/common/lighting/advanced/gl/dbgDepthVisualizeP.glsl b/Templates/Full/game/shaders/common/lighting/advanced/gl/dbgDepthVisualizeP.glsl index 7c1754097..eb3d6f761 100644 --- a/Templates/Full/game/shaders/common/lighting/advanced/gl/dbgDepthVisualizeP.glsl +++ b/Templates/Full/game/shaders/common/lighting/advanced/gl/dbgDepthVisualizeP.glsl @@ -24,13 +24,13 @@ #include "shadergen:/autogenConditioners.h" in vec2 uv0; -uniform sampler2D prepassBuffer; +uniform sampler2D prepassTex; uniform sampler1D depthViz; out vec4 OUT_col; void main() { - float depth = prepassUncondition( prepassBuffer, uv0 ).w; + float depth = prepassUncondition( prepassTex, uv0 ).w; OUT_col = vec4( texture( depthViz, depth ).rgb, 1.0 ); } \ No newline at end of file diff --git a/Templates/Full/game/shaders/common/lighting/advanced/gl/dbgNormalVisualizeP.glsl b/Templates/Full/game/shaders/common/lighting/advanced/gl/dbgNormalVisualizeP.glsl index dfc611e88..84ea4d3fb 100644 --- a/Templates/Full/game/shaders/common/lighting/advanced/gl/dbgNormalVisualizeP.glsl +++ b/Templates/Full/game/shaders/common/lighting/advanced/gl/dbgNormalVisualizeP.glsl @@ -24,12 +24,12 @@ #include "shadergen:/autogenConditioners.h" in vec2 uv0; -uniform sampler2D prepassBuffer; +uniform sampler2D prepassTex; out vec4 OUT_col; void main() { - vec3 normal = prepassUncondition( prepassBuffer, uv0 ).xyz; + vec3 normal = prepassUncondition( prepassTex, uv0 ).xyz; OUT_col = vec4( ( normal + 1.0 ) * 0.5, 1.0 ); } \ No newline at end of file diff --git a/Templates/Full/game/shaders/common/lighting/advanced/gl/deferredColorShaderP.glsl b/Templates/Full/game/shaders/common/lighting/advanced/gl/deferredColorShaderP.glsl new file mode 100644 index 000000000..85c553089 --- /dev/null +++ b/Templates/Full/game/shaders/common/lighting/advanced/gl/deferredColorShaderP.glsl @@ -0,0 +1,37 @@ +//----------------------------------------------------------------------------- +// Copyright (c) 2012 GarageGames, LLC +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. +//----------------------------------------------------------------------------- + +layout (location = 0) out vec4 col; +layout (location = 1) out vec4 col1; +layout (location = 2) out vec4 col2; + +//----------------------------------------------------------------------------- +// Main +//----------------------------------------------------------------------------- +void main() +{ + col = vec4(0.0, 0.0, 0.0, 0.0); + col1 = vec4(1.0, 1.0, 1.0, 1.0); + + // Draw on color buffer. + col2 = vec4(1.0, 0.0, 0.0, 1.0); +} diff --git a/Templates/Full/game/shaders/common/postFx/hdr/finalPassCombineP.hlsl b/Templates/Full/game/shaders/common/postFx/hdr/finalPassCombineP.hlsl index 9541f39cb..cb71f01c2 100644 --- a/Templates/Full/game/shaders/common/postFx/hdr/finalPassCombineP.hlsl +++ b/Templates/Full/game/shaders/common/postFx/hdr/finalPassCombineP.hlsl @@ -20,6 +20,7 @@ // IN THE SOFTWARE. //----------------------------------------------------------------------------- +#include "shadergen:/autogenConditioners.h" #include "../../torque.hlsl" #include "../postFx.hlsl" @@ -27,6 +28,7 @@ uniform sampler2D sceneTex : register( s0 ); uniform sampler2D luminanceTex : register( s1 ); uniform sampler2D bloomTex : register( s2 ); uniform sampler1D colorCorrectionTex : register( s3 ); +uniform sampler2D prepassTex : register(S4); uniform float2 texSize0; uniform float2 texSize2; @@ -83,13 +85,16 @@ float4 main( PFXVertToPix IN ) : COLOR0 } // Add the bloom effect. - sample += g_fBloomScale * bloom; + float depth = prepassUncondition( prepassTex, IN.uv0 ).w; + if (depth>0.9999) + sample += g_fBloomScale * bloom; // Apply the color correction. sample.r = tex1D( colorCorrectionTex, sample.r ).r; sample.g = tex1D( colorCorrectionTex, sample.g ).g; sample.b = tex1D( colorCorrectionTex, sample.b ).b; + // Apply gamma correction sample.rgb = pow( abs(sample.rgb), g_fOneOverGamma ); diff --git a/Templates/Full/game/shaders/common/postFx/hdr/gl/finalPassCombineP.glsl b/Templates/Full/game/shaders/common/postFx/hdr/gl/finalPassCombineP.glsl index 123c831f3..24a516e79 100644 --- a/Templates/Full/game/shaders/common/postFx/hdr/gl/finalPassCombineP.glsl +++ b/Templates/Full/game/shaders/common/postFx/hdr/gl/finalPassCombineP.glsl @@ -23,11 +23,13 @@ #include "../../../gl/torque.glsl" #include "../../../gl/hlslCompat.glsl" #include "../../gl/postFX.glsl" +#include "shadergen:/autogenConditioners.h" uniform sampler2D sceneTex; uniform sampler2D luminanceTex; uniform sampler2D bloomTex; uniform sampler1D colorCorrectionTex; +uniform sampler2D prepassTex; uniform vec2 texSize0; uniform vec2 texSize2; @@ -86,7 +88,9 @@ void main() } // Add the bloom effect. - _sample += g_fBloomScale * bloom; + float depth = prepassUncondition( prepassTex, IN_uv0 ).w; + if (depth>0.9999) + _sample += g_fBloomScale * bloom; // Apply the color correction. _sample.r = texture( colorCorrectionTex, _sample.r ).r; diff --git a/Templates/Full/game/shaders/common/scatterSkyP.hlsl b/Templates/Full/game/shaders/common/scatterSkyP.hlsl index 572abdd74..1a9433c73 100644 --- a/Templates/Full/game/shaders/common/scatterSkyP.hlsl +++ b/Templates/Full/game/shaders/common/scatterSkyP.hlsl @@ -62,6 +62,6 @@ float4 main( Conn In ) : COLOR0 Out = lerp( color, nightSkyColor, nightInterpAndExposure.y ); Out.a = 1; - + Out = saturate(Out); return hdrEncode( Out ); } From 9a2a5b2a90515eda9684fc9d042deda5a0d77cb4 Mon Sep 17 00:00:00 2001 From: Anis Date: Thu, 18 Feb 2016 16:49:06 +0100 Subject: [PATCH 081/109] compile fix. --- Engine/source/gfx/sim/debugDraw.h | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/Engine/source/gfx/sim/debugDraw.h b/Engine/source/gfx/sim/debugDraw.h index c650417b0..a07f52ca4 100644 --- a/Engine/source/gfx/sim/debugDraw.h +++ b/Engine/source/gfx/sim/debugDraw.h @@ -124,7 +124,10 @@ public: void drawLine(const Point3F &a, const Point3F &b, const ColorF &color = ColorF(1.0f,1.0f,1.0f)); void drawTri(const Point3F &a, const Point3F &b, const Point3F &c, const ColorF &color = ColorF(1.0f,1.0f,1.0f)); void drawText(const Point3F& pos, const String& text, const ColorF &color = ColorF(1.0f,1.0f,1.0f)); - + void drawCapsule(const Point3F &a, const F32 &radius, const F32 &height, const ColorF &color = ColorF(1.0f, 1.0f, 1.0f)); + void drawDirectionLine(const Point3F &a, const Point3F &b, const ColorF &color = ColorF(1.0f, 1.0f, 1.0f)); + void drawOutlinedText(const Point3F& pos, const String& text, const ColorF &color = ColorF(1.0f, 1.0f, 1.0f), const ColorF &colorOutline = ColorF(0.0f, 0.0f, 0.0f)); + /// Render a wireframe view of the given polyhedron. void drawPolyhedron( const AnyPolyhedron& polyhedron, const ColorF& color = ColorF( 1.f, 1.f, 1.f ) ); From c4590f6e3db39b57e08e56501dbd62f08cf18a2d Mon Sep 17 00:00:00 2001 From: Anis Date: Thu, 18 Feb 2016 23:30:09 +0100 Subject: [PATCH 082/109] Update guiTextEditCtrl.cpp --- Engine/source/gui/controls/guiTextEditCtrl.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Engine/source/gui/controls/guiTextEditCtrl.cpp b/Engine/source/gui/controls/guiTextEditCtrl.cpp index 7224f3eb8..6c1935bb1 100644 --- a/Engine/source/gui/controls/guiTextEditCtrl.cpp +++ b/Engine/source/gui/controls/guiTextEditCtrl.cpp @@ -1260,7 +1260,7 @@ void GuiTextEditCtrl::onRender(Point2I offset, const RectI &updateRect) if ( mProfile->mOpaque ) { if (!mTextValid) - GFX->getDrawUtil()->drawRectFill(ctrlRect, mProfile->mFillColorNA); + GFX->getDrawUtil()->drawRectFill(ctrlRect, mProfile->mFillColorERR); else if (isFirstResponder()) GFX->getDrawUtil()->drawRectFill(ctrlRect, mProfile->mFillColorHL); else @@ -1272,7 +1272,7 @@ void GuiTextEditCtrl::onRender(Point2I offset, const RectI &updateRect) { renderBorder(ctrlRect, mProfile); if (!mTextValid) - GFX->getDrawUtil()->drawRectFill(ctrlRect, mProfile->mFillColorNA); + GFX->getDrawUtil()->drawRectFill(ctrlRect, mProfile->mFillColorERR); } drawText( ctrlRect, isFirstResponder() ); From 3ca67b31487dc44a72fbdff2ca4abadb276e66bb Mon Sep 17 00:00:00 2001 From: Anis Date: Thu, 18 Feb 2016 23:32:19 +0100 Subject: [PATCH 083/109] Update guiTypes.h --- Engine/source/gui/core/guiTypes.h | 1 + 1 file changed, 1 insertion(+) diff --git a/Engine/source/gui/core/guiTypes.h b/Engine/source/gui/core/guiTypes.h index c9dc36bad..51f33d21b 100644 --- a/Engine/source/gui/core/guiTypes.h +++ b/Engine/source/gui/core/guiTypes.h @@ -385,6 +385,7 @@ public: ColorI mFillColor; ///< Fill color, this is used to fill the bounds of the control if it is opaque ColorI mFillColorHL; ///< This is used instead of mFillColor if the object is highlighted ColorI mFillColorNA; ///< This is used instead of mFillColor if the object is not active or disabled + ColorI mFillColorERR; ///< This is used instead of mFillColor if the object has an error or is invalid ColorI mFillColorSEL; ///< This is used instead of mFillColor if the object is selected S32 mBorder; ///< For most controls, if mBorder is > 0 a border will be drawn, some controls use this to draw different types of borders however @see guiDefaultControlRender.cc From df283a270999683a0507c4a4b3cfe1a6d6583f04 Mon Sep 17 00:00:00 2001 From: Anis Date: Thu, 18 Feb 2016 23:33:46 +0100 Subject: [PATCH 084/109] Update guiTypes.cpp --- Engine/source/gui/core/guiTypes.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Engine/source/gui/core/guiTypes.cpp b/Engine/source/gui/core/guiTypes.cpp index dbf96c517..08c647f36 100644 --- a/Engine/source/gui/core/guiTypes.cpp +++ b/Engine/source/gui/core/guiTypes.cpp @@ -269,6 +269,7 @@ GuiControlProfile::GuiControlProfile(void) : mFillColor(255,0,255,255), mFillColorHL(255,0,255,255), mFillColorNA(255,0,255,255), + mFillColorERR(255,0,0,255), mFillColorSEL(255,0,255,255), mBorderColor(255,0,255,255), mBorderColorHL(255,0,255,255), @@ -334,6 +335,7 @@ GuiControlProfile::GuiControlProfile(void) : mFillColor = def->mFillColor; mFillColorHL = def->mFillColorHL; mFillColorNA = def->mFillColorNA; + mFillColorERR = def->mFillColorERR; mFillColorSEL = def->mFillColorSEL; mBorder = def->mBorder; @@ -398,6 +400,7 @@ void GuiControlProfile::initPersistFields() addField("fillColor", TypeColorI, Offset(mFillColor, GuiControlProfile)); addField("fillColorHL", TypeColorI, Offset(mFillColorHL, GuiControlProfile)); addField("fillColorNA", TypeColorI, Offset(mFillColorNA, GuiControlProfile)); + addField("fillColorERR", TypeColorI, Offset(mFillColorERR, GuiControlProfile)); addField("fillColorSEL", TypeColorI, Offset(mFillColorSEL, GuiControlProfile)); addField("border", TypeS32, Offset(mBorder, GuiControlProfile), "Border type (0=no border)." ); From 6f47cb7dfae34f43d88cd520310f4649005cea34 Mon Sep 17 00:00:00 2001 From: Anis Date: Fri, 19 Feb 2016 00:34:07 +0100 Subject: [PATCH 085/109] Update guiControl.h --- Engine/source/gui/core/guiControl.h | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Engine/source/gui/core/guiControl.h b/Engine/source/gui/core/guiControl.h index dc2653ff9..ca873878d 100644 --- a/Engine/source/gui/core/guiControl.h +++ b/Engine/source/gui/core/guiControl.h @@ -121,9 +121,9 @@ class GuiControl : public SimGroup horizResizeLeft, ///< fixed on the right and width horizResizeCenter, horizResizeRelative, ///< resize relative - horizResizeAspectLeft, ///< resize relative to hieght delta (offset Left) - horizResizeAspectRight, ///< resize relative to hieght delta (offset Right) - horizResizeAspectCenter, ///< resize relative to hieght delta (Centered) + horizResizeAspectLeft, ///< resize relative to height delta (offset Left) + horizResizeAspectRight, ///< resize relative to height delta (offset Right) + horizResizeAspectCenter, ///< resize relative to height delta (Centered) horizResizeWindowRelative ///< resize window relative }; enum vertSizingOptions From 612d932372efa0b54d730317e15bba49812a3a57 Mon Sep 17 00:00:00 2001 From: Azaezel Date: Fri, 19 Feb 2016 17:34:27 -0600 Subject: [PATCH 086/109] Revert "Update navMesh.cpp" This reverts commit f3ff1995549cda16929d761a0131214452e5187f. --- Engine/source/navigation/navMesh.cpp | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/Engine/source/navigation/navMesh.cpp b/Engine/source/navigation/navMesh.cpp index 22a47a9f3..74d71dc77 100644 --- a/Engine/source/navigation/navMesh.cpp +++ b/Engine/source/navigation/navMesh.cpp @@ -123,6 +123,28 @@ DefineConsoleFunction(NavMeshUpdateAll, void, (S32 objid, bool remove), (0, fals obj->enableCollision(); } +DefineConsoleFunction(NavMeshUpdateAroundObject, void, (S32 objid, bool remove), (0, false), + "@brief Update all NavMesh tiles that intersect the given object's world box.") +{ + SceneObject *obj; + if (!Sim::findObject(objid, obj)) + return; + if (remove) + obj->disableCollision(); + SimSet *set = NavMesh::getServerSet(); + for (U32 i = 0; i < set->size(); i++) + { + NavMesh *m = dynamic_cast(set->at(i)); + if (m) + { + m->cancelBuild(); + m->buildTiles(obj->getWorldBox()); + } + } + if (remove) + obj->enableCollision(); +} + DefineConsoleFunction(NavMeshUpdateOne, void, (S32 meshid, S32 objid, bool remove), (0, 0, false), "@brief Update all tiles in a given NavMesh that intersect the given object's world box.") { From 28d303c5eaa0f2fb41bf75770ee4e3a1c4122b12 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Du=C5=A1an=20Joci=C4=87?= Date: Sat, 20 Feb 2016 21:28:18 +0100 Subject: [PATCH 087/109] Added immutable vertex and index buffers. --- Engine/source/gfx/D3D9/gfxD3D9Device.cpp | 28 ++++++++++++-- Engine/source/gfx/D3D9/gfxD3D9Device.h | 6 ++- .../gfx/D3D9/pc/gfxD3D9PrimitiveBuffer.pc.cpp | 1 + Engine/source/gfx/Null/gfxNullDevice.cpp | 6 ++- Engine/source/gfx/Null/gfxNullDevice.h | 6 ++- Engine/source/gfx/gfxDevice.h | 6 ++- Engine/source/gfx/gfxEnums.h | 7 ++-- Engine/source/gfx/gfxPrimitiveBuffer.cpp | 13 +++++++ Engine/source/gfx/gfxPrimitiveBuffer.h | 2 + Engine/source/gfx/gl/gfxGLDevice.cpp | 37 +++++++++++++++---- Engine/source/gfx/gl/gfxGLDevice.h | 5 ++- Engine/source/gfx/gl/gfxGLEnumTranslate.cpp | 1 + .../renderInstance/renderParticleMgr.cpp | 12 ++---- 13 files changed, 98 insertions(+), 32 deletions(-) diff --git a/Engine/source/gfx/D3D9/gfxD3D9Device.cpp b/Engine/source/gfx/D3D9/gfxD3D9Device.cpp index 2ad550064..d3eb8ed8a 100644 --- a/Engine/source/gfx/D3D9/gfxD3D9Device.cpp +++ b/Engine/source/gfx/D3D9/gfxD3D9Device.cpp @@ -731,7 +731,8 @@ void GFXD3D9Device::setShader( GFXShader *shader, bool force ) //----------------------------------------------------------------------------- GFXPrimitiveBuffer * GFXD3D9Device::allocPrimitiveBuffer( U32 numIndices, U32 numPrimitives, - GFXBufferType bufferType ) + GFXBufferType bufferType, + void* data ) { // Allocate a buffer to return GFXD3D9PrimitiveBuffer * res = new GFXD3D9PrimitiveBuffer(this, numIndices, numPrimitives, bufferType); @@ -741,12 +742,13 @@ GFXPrimitiveBuffer * GFXD3D9Device::allocPrimitiveBuffer( U32 numIndices, D3DPOOL pool = D3DPOOL_DEFAULT; // Assumptions: - // - static buffers are write once, use many + // - static buffers are write rarely, use many // - dynamic buffers are write many, use many // - volatile buffers are write once, use once // You may never read from a buffer. switch(bufferType) { + case GFXBufferTypeImmutable: case GFXBufferTypeStatic: pool = isD3D9Ex() ? D3DPOOL_DEFAULT : D3DPOOL_MANAGED; break; @@ -781,6 +783,14 @@ GFXPrimitiveBuffer * GFXD3D9Device::allocPrimitiveBuffer( U32 numIndices, D3D9Assert(mD3DDevice->CreateIndexBuffer( sizeof(U16) * numIndices , usage, GFXD3D9IndexFormat[GFXIndexFormat16], pool, &res->ib, 0), "Failed to allocate an index buffer."); } + + if(data) + { + void* dest; + res->lock(0, numIndices, &dest); + dMemcpy(dest, data, sizeof(U16) * numIndices); + res->unlock(); + } return res; } @@ -791,7 +801,8 @@ GFXPrimitiveBuffer * GFXD3D9Device::allocPrimitiveBuffer( U32 numIndices, GFXVertexBuffer * GFXD3D9Device::allocVertexBuffer( U32 numVerts, const GFXVertexFormat *vertexFormat, U32 vertSize, - GFXBufferType bufferType ) + GFXBufferType bufferType, + void* data) { PROFILE_SCOPE( GFXD3D9Device_allocVertexBuffer ); @@ -808,7 +819,7 @@ GFXVertexBuffer * GFXD3D9Device::allocVertexBuffer( U32 numVerts, res->mNumVerts = 0; // Assumptions: - // - static buffers are write once, use many + // - static buffers are write rarely, use many // - dynamic buffers are write many, use many // - volatile buffers are write once, use once // You may never read from a buffer. @@ -850,6 +861,15 @@ GFXVertexBuffer * GFXD3D9Device::allocVertexBuffer( U32 numVerts, } res->mNumVerts = numVerts; + + if(data) + { + void* dest; + res->lock(0, numVerts, &dest); + dMemcpy(dest, data, vertSize * numVerts); + res->unlock(); + } + return res; } diff --git a/Engine/source/gfx/D3D9/gfxD3D9Device.h b/Engine/source/gfx/D3D9/gfxD3D9Device.h index c928b1a0d..ef1c9be5d 100644 --- a/Engine/source/gfx/D3D9/gfxD3D9Device.h +++ b/Engine/source/gfx/D3D9/gfxD3D9Device.h @@ -298,10 +298,12 @@ public: virtual GFXVertexBuffer* allocVertexBuffer( U32 numVerts, const GFXVertexFormat *vertexFormat, U32 vertSize, - GFXBufferType bufferType ); + GFXBufferType bufferType, + void* data = NULL ); virtual GFXPrimitiveBuffer *allocPrimitiveBuffer( U32 numIndices, U32 numPrimitives, - GFXBufferType bufferType ); + GFXBufferType bufferType, + void* data = NULL ); virtual void deallocVertexBuffer( GFXD3D9VertexBuffer *vertBuff ); virtual GFXVertexDecl* allocVertexDecl( const GFXVertexFormat *vertexFormat ); virtual void setVertexDecl( const GFXVertexDecl *decl ); diff --git a/Engine/source/gfx/D3D9/pc/gfxD3D9PrimitiveBuffer.pc.cpp b/Engine/source/gfx/D3D9/pc/gfxD3D9PrimitiveBuffer.pc.cpp index 21bb586bb..32251438d 100644 --- a/Engine/source/gfx/D3D9/pc/gfxD3D9PrimitiveBuffer.pc.cpp +++ b/Engine/source/gfx/D3D9/pc/gfxD3D9PrimitiveBuffer.pc.cpp @@ -31,6 +31,7 @@ void GFXD3D9PrimitiveBuffer::lock(U32 indexStart, U32 indexEnd, void **indexPtr) U32 flags=0; switch(mBufferType) { + case GFXBufferTypeImmutable: case GFXBufferTypeStatic: // flags |= D3DLOCK_DISCARD; break; diff --git a/Engine/source/gfx/Null/gfxNullDevice.cpp b/Engine/source/gfx/Null/gfxNullDevice.cpp index 315336c24..f22e3bc7a 100644 --- a/Engine/source/gfx/Null/gfxNullDevice.cpp +++ b/Engine/source/gfx/Null/gfxNullDevice.cpp @@ -276,14 +276,16 @@ GFXNullDevice::~GFXNullDevice() GFXVertexBuffer *GFXNullDevice::allocVertexBuffer( U32 numVerts, const GFXVertexFormat *vertexFormat, U32 vertSize, - GFXBufferType bufferType ) + GFXBufferType bufferType, + void* data ) { return new GFXNullVertexBuffer(GFX, numVerts, vertexFormat, vertSize, bufferType); } GFXPrimitiveBuffer *GFXNullDevice::allocPrimitiveBuffer( U32 numIndices, U32 numPrimitives, - GFXBufferType bufferType) + GFXBufferType bufferType, + void* data ) { return new GFXNullPrimitiveBuffer(GFX, numIndices, numPrimitives, bufferType); } diff --git a/Engine/source/gfx/Null/gfxNullDevice.h b/Engine/source/gfx/Null/gfxNullDevice.h index 5e137cf01..28d6ad4e9 100644 --- a/Engine/source/gfx/Null/gfxNullDevice.h +++ b/Engine/source/gfx/Null/gfxNullDevice.h @@ -115,10 +115,12 @@ protected: virtual GFXVertexBuffer *allocVertexBuffer( U32 numVerts, const GFXVertexFormat *vertexFormat, U32 vertSize, - GFXBufferType bufferType ); + GFXBufferType bufferType, + void* data = NULL ); virtual GFXPrimitiveBuffer *allocPrimitiveBuffer( U32 numIndices, U32 numPrimitives, - GFXBufferType bufferType ); + GFXBufferType bufferType, + void* data = NULL ); virtual GFXVertexDecl* allocVertexDecl( const GFXVertexFormat *vertexFormat ) { return NULL; } virtual void setVertexDecl( const GFXVertexDecl *decl ) { } diff --git a/Engine/source/gfx/gfxDevice.h b/Engine/source/gfx/gfxDevice.h index b9ff01e8c..e8ca6739b 100644 --- a/Engine/source/gfx/gfxDevice.h +++ b/Engine/source/gfx/gfxDevice.h @@ -637,7 +637,8 @@ protected: virtual GFXVertexBuffer *allocVertexBuffer( U32 numVerts, const GFXVertexFormat *vertexFormat, U32 vertSize, - GFXBufferType bufferType ) = 0; + GFXBufferType bufferType, + void* data = NULL ) = 0; /// Called from GFXVertexFormat to allocate the hardware /// specific vertex declaration for rendering. @@ -674,7 +675,8 @@ protected: /// @note All index buffers use unsigned 16-bit indices. virtual GFXPrimitiveBuffer *allocPrimitiveBuffer( U32 numIndices, U32 numPrimitives, - GFXBufferType bufferType ) = 0; + GFXBufferType bufferType, + void* data = NULL ) = 0; /// @} diff --git a/Engine/source/gfx/gfxEnums.h b/Engine/source/gfx/gfxEnums.h index 9dfba2f73..e0c560ac8 100644 --- a/Engine/source/gfx/gfxEnums.h +++ b/Engine/source/gfx/gfxEnums.h @@ -39,8 +39,8 @@ enum GFXBufferType { - GFXBufferTypeStatic, ///< Static vertex buffers are created and filled one time. - ///< incur a performance penalty. Resizing a static vertex buffer is not + GFXBufferTypeStatic, ///< Static vertex buffers are created and rarely updated. + ///< Updating might incur a performance penalty. Resizing a static vertex buffer is not ///< allowed. GFXBufferTypeDynamic, ///< Dynamic vertex buffers are meant for vertices that can be changed ///< often. Vertices written into dynamic vertex buffers will remain valid @@ -48,7 +48,8 @@ enum GFXBufferType ///< allowed. GFXBufferTypeVolatile, ///< Volatile vertex or index buffers are meant for vertices or indices that are essentially ///< only used once. They can be resized without any performance penalty. - + GFXBufferTypeImmutable, ///< Immutable buffers must specify the data when creating the buffer. Cannot be modified. + GFXBufferType_COUNT ///< Number of buffer types. }; diff --git a/Engine/source/gfx/gfxPrimitiveBuffer.cpp b/Engine/source/gfx/gfxPrimitiveBuffer.cpp index 9a0a09119..2a06f53e2 100644 --- a/Engine/source/gfx/gfxPrimitiveBuffer.cpp +++ b/Engine/source/gfx/gfxPrimitiveBuffer.cpp @@ -80,3 +80,16 @@ void GFXPrimitiveBufferHandle::set(GFXDevice *theDevice, U32 indexCount, U32 pri getPointer()->mDebugCreationPath = desc; #endif } + +//----------------------------------------------------------------------------- +// immutable +//----------------------------------------------------------------------------- +void GFXPrimitiveBufferHandle::immutable(GFXDevice *theDevice, U32 indexCount, U32 primitiveCount, void* data, String desc) +{ + StrongRefPtr::operator=( theDevice->allocPrimitiveBuffer(indexCount, primitiveCount, GFXBufferTypeImmutable, data) ); + +#ifdef TORQUE_DEBUG + if( desc.isNotEmpty() ) + getPointer()->mDebugCreationPath = desc; +#endif +} diff --git a/Engine/source/gfx/gfxPrimitiveBuffer.h b/Engine/source/gfx/gfxPrimitiveBuffer.h index c2955786c..9fa6ed14f 100644 --- a/Engine/source/gfx/gfxPrimitiveBuffer.h +++ b/Engine/source/gfx/gfxPrimitiveBuffer.h @@ -140,6 +140,8 @@ public: } void set(GFXDevice *theDevice, U32 indexCount, U32 primitiveCount, GFXBufferType bufferType, String desc = String::EmptyString ); + + void immutable(GFXDevice *theDevice, U32 indexCount, U32 primitiveCount, void* data, String desc = String::EmptyString ); void lock(U16 **indexBuffer, GFXPrimitive **primitiveBuffer = NULL, U32 indexStart = 0, U32 indexEnd = 0) { diff --git a/Engine/source/gfx/gl/gfxGLDevice.cpp b/Engine/source/gfx/gl/gfxGLDevice.cpp index 9cec609d1..b6227faad 100644 --- a/Engine/source/gfx/gl/gfxGLDevice.cpp +++ b/Engine/source/gfx/gl/gfxGLDevice.cpp @@ -355,23 +355,46 @@ GFXPrimitiveBuffer* GFXGLDevice::findVolatilePBO(U32 numIndices, U32 numPrimitiv GFXVertexBuffer *GFXGLDevice::allocVertexBuffer( U32 numVerts, const GFXVertexFormat *vertexFormat, U32 vertSize, - GFXBufferType bufferType ) + GFXBufferType bufferType, + void* data = NULL ) { if(bufferType == GFXBufferTypeVolatile) return findVolatileVBO(numVerts, vertexFormat, vertSize); GFXGLVertexBuffer* buf = new GFXGLVertexBuffer( GFX, numVerts, vertexFormat, vertSize, bufferType ); - buf->registerResourceWithDevice(this); + buf->registerResourceWithDevice(this); + + if(data) + { + void* dest; + buf->lock(0, numVerts, &dest); + dMemcpy(dest, data, vertSize * numVerts); + buf->unlock(); + } + return buf; } -GFXPrimitiveBuffer *GFXGLDevice::allocPrimitiveBuffer( U32 numIndices, U32 numPrimitives, GFXBufferType bufferType ) +GFXPrimitiveBuffer *GFXGLDevice::allocPrimitiveBuffer( U32 numIndices, U32 numPrimitives, GFXBufferType bufferType, void* data ) { + GFXPrimitiveBuffer* buf if(bufferType == GFXBufferTypeVolatile) - return findVolatilePBO(numIndices, numPrimitives); - - GFXGLPrimitiveBuffer* buf = new GFXGLPrimitiveBuffer(GFX, numIndices, numPrimitives, bufferType); - buf->registerResourceWithDevice(this); + { + buf = findVolatilePBO(numIndices, numPrimitives); + } + else + { + buf = new GFXGLPrimitiveBuffer(GFX, numIndices, numPrimitives, bufferType); + buf->registerResourceWithDevice(this); + } + + if(data) + { + void* dest; + buf->lock(0, numIndices, &dest); + dMemcpy(dest, data, sizeof(U16) * numIndices); + buf->unlock(); + } return buf; } diff --git a/Engine/source/gfx/gl/gfxGLDevice.h b/Engine/source/gfx/gl/gfxGLDevice.h index 72193835d..902bfb3f6 100644 --- a/Engine/source/gfx/gl/gfxGLDevice.h +++ b/Engine/source/gfx/gl/gfxGLDevice.h @@ -173,8 +173,9 @@ protected: virtual GFXVertexBuffer *allocVertexBuffer( U32 numVerts, const GFXVertexFormat *vertexFormat, U32 vertSize, - GFXBufferType bufferType ); - virtual GFXPrimitiveBuffer *allocPrimitiveBuffer( U32 numIndices, U32 numPrimitives, GFXBufferType bufferType ); + GFXBufferType bufferType, + void* data = NULL); + virtual GFXPrimitiveBuffer *allocPrimitiveBuffer( U32 numIndices, U32 numPrimitives, GFXBufferType bufferType, void* data = NULL ); // NOTE: The GL device doesn't need a vertex declaration at // this time, but we need to return something to keep the system diff --git a/Engine/source/gfx/gl/gfxGLEnumTranslate.cpp b/Engine/source/gfx/gl/gfxGLEnumTranslate.cpp index 19f6e1c69..e78d807c1 100644 --- a/Engine/source/gfx/gl/gfxGLEnumTranslate.cpp +++ b/Engine/source/gfx/gl/gfxGLEnumTranslate.cpp @@ -45,6 +45,7 @@ void GFXGLEnumTranslate::init() GFXGLBufferType[GFXBufferTypeStatic] = GL_STATIC_DRAW; GFXGLBufferType[GFXBufferTypeDynamic] = GL_DYNAMIC_DRAW; GFXGLBufferType[GFXBufferTypeVolatile] = GL_STREAM_DRAW; + GFXGLBufferType[GFXBufferTypeImmutable] = GL_STATIC_DRAW; // Primitives GFXGLPrimType[GFXPointList] = GL_POINTS; diff --git a/Engine/source/renderInstance/renderParticleMgr.cpp b/Engine/source/renderInstance/renderParticleMgr.cpp index 2b475ab7f..dcae9756c 100644 --- a/Engine/source/renderInstance/renderParticleMgr.cpp +++ b/Engine/source/renderInstance/renderParticleMgr.cpp @@ -338,14 +338,10 @@ void RenderParticleMgr::render( SceneRenderState *state ) void RenderParticleMgr::_initGFXResources() { // Screen quad - U16 *prims = NULL; - mScreenQuadPrimBuff.set(GFX, 4, 2, GFXBufferTypeStatic); - mScreenQuadPrimBuff.lock(&prims); - (*prims++) = 0; - (*prims++) = 1; - (*prims++) = 2; - (*prims++) = 3; - mScreenQuadPrimBuff.unlock(); + U16 prims [] = { + 0, 1, 2, 3, + }; + mScreenQuadPrimBuff.immutable(GFX, 4, 2, prims); mScreenQuadVertBuff.set(GFX, 4, GFXBufferTypeStatic); CompositeQuadVert *verts = mScreenQuadVertBuff.lock(); From 36daca8d8e4b641e1d653fb0ebc581cb97b5ada5 Mon Sep 17 00:00:00 2001 From: rextimmy Date: Sun, 21 Feb 2016 15:10:58 +1000 Subject: [PATCH 088/109] Corrected FeatureSet::getNextFeatureIndex and ShaderFeature::getProcessIndex signed mismatch. --- Engine/source/shaderGen/featureSet.cpp | 2 +- Engine/source/shaderGen/featureSet.h | 2 +- Engine/source/shaderGen/shaderFeature.h | 2 +- Engine/source/terrain/hlsl/terrFeatureHLSL.cpp | 10 +++++----- 4 files changed, 8 insertions(+), 8 deletions(-) diff --git a/Engine/source/shaderGen/featureSet.cpp b/Engine/source/shaderGen/featureSet.cpp index 7189beacc..2eff50e1c 100644 --- a/Engine/source/shaderGen/featureSet.cpp +++ b/Engine/source/shaderGen/featureSet.cpp @@ -170,7 +170,7 @@ void FeatureSet::removeFeature( const FeatureType &type ) } } -U32 FeatureSet::getNextFeatureIndex( const FeatureType &type, S32 index ) const +S32 FeatureSet::getNextFeatureIndex( const FeatureType &type, S32 index ) const { for ( U32 i=0; i < mFeatures.size(); i++ ) { diff --git a/Engine/source/shaderGen/featureSet.h b/Engine/source/shaderGen/featureSet.h index e8920dc67..03b46f62a 100644 --- a/Engine/source/shaderGen/featureSet.h +++ b/Engine/source/shaderGen/featureSet.h @@ -106,7 +106,7 @@ public: void removeFeature( const FeatureType &type ); /// - U32 getNextFeatureIndex( const FeatureType &type, S32 index ) const; + S32 getNextFeatureIndex( const FeatureType &type, S32 index ) const; /// Removes features that are not in the input set. void filter( const FeatureSet &features ); diff --git a/Engine/source/shaderGen/shaderFeature.h b/Engine/source/shaderGen/shaderFeature.h index f18fae7f9..c6ac4955d 100644 --- a/Engine/source/shaderGen/shaderFeature.h +++ b/Engine/source/shaderGen/shaderFeature.h @@ -153,7 +153,7 @@ public: void setProcessIndex( S32 index ) { mProcessIndex = index; } /// - U32 getProcessIndex() const { return mProcessIndex; } + S32 getProcessIndex() const { return mProcessIndex; } //----------------------------------------------------------------------- // Virtual Functions diff --git a/Engine/source/terrain/hlsl/terrFeatureHLSL.cpp b/Engine/source/terrain/hlsl/terrFeatureHLSL.cpp index 6ef1c2009..d993f4bd8 100644 --- a/Engine/source/terrain/hlsl/terrFeatureHLSL.cpp +++ b/Engine/source/terrain/hlsl/terrFeatureHLSL.cpp @@ -298,7 +298,7 @@ TerrainDetailMapFeatHLSL::TerrainDetailMapFeatHLSL() void TerrainDetailMapFeatHLSL::processVert( Vector &componentList, const MaterialFeatureData &fd ) { - const U32 detailIndex = getProcessIndex(); + const S32 detailIndex = getProcessIndex(); // Grab incoming texture coords... the base map feature // made sure this was created. @@ -383,7 +383,7 @@ void TerrainDetailMapFeatHLSL::processVert( Vector &component void TerrainDetailMapFeatHLSL::processPix( Vector &componentList, const MaterialFeatureData &fd ) { - const U32 detailIndex = getProcessIndex(); + const S32 detailIndex = getProcessIndex(); Var *inTex = getVertTexCoord( "texCoord" ); MultiLine *meta = new MultiLine; @@ -615,7 +615,7 @@ TerrainMacroMapFeatHLSL::TerrainMacroMapFeatHLSL() void TerrainMacroMapFeatHLSL::processVert( Vector &componentList, const MaterialFeatureData &fd ) { - const U32 detailIndex = getProcessIndex(); + const S32 detailIndex = getProcessIndex(); // Grab incoming texture coords... the base map feature // made sure this was created. @@ -673,7 +673,7 @@ void TerrainMacroMapFeatHLSL::processVert( Vector &componentL void TerrainMacroMapFeatHLSL::processPix( Vector &componentList, const MaterialFeatureData &fd ) { - const U32 detailIndex = getProcessIndex(); + const S32 detailIndex = getProcessIndex(); Var *inTex = getVertTexCoord( "texCoord" ); MultiLine *meta = new MultiLine; @@ -900,7 +900,7 @@ void TerrainNormalMapFeatHLSL::processPix( Vector &component meta->addStatement( new GenOp( " @ = @[2];\r\n", new DecOp( gbNormal ), viewToTangent ) ); } - const U32 normalIndex = getProcessIndex(); + const S32 normalIndex = getProcessIndex(); Var *detailBlend = (Var*)LangElement::find( String::ToString( "detailBlend%d", normalIndex ) ); AssertFatal( detailBlend, "The detail blend is missing!" ); From 2383e8e43e306f902e234049ef0731482067e6dd Mon Sep 17 00:00:00 2001 From: rextimmy Date: Sun, 21 Feb 2016 22:24:05 +1000 Subject: [PATCH 089/109] Fix for TerrainFeatGLSL getProcessIndex() signed mismatch --- Engine/source/terrain/glsl/terrFeatureGLSL.cpp | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/Engine/source/terrain/glsl/terrFeatureGLSL.cpp b/Engine/source/terrain/glsl/terrFeatureGLSL.cpp index 5f32359a8..dd67124c6 100644 --- a/Engine/source/terrain/glsl/terrFeatureGLSL.cpp +++ b/Engine/source/terrain/glsl/terrFeatureGLSL.cpp @@ -298,7 +298,7 @@ TerrainDetailMapFeatGLSL::TerrainDetailMapFeatGLSL() void TerrainDetailMapFeatGLSL::processVert( Vector &componentList, const MaterialFeatureData &fd ) { - const U32 detailIndex = getProcessIndex(); + const S32 detailIndex = getProcessIndex(); // Grab incoming texture coords... the base map feature // made sure this was created. @@ -383,7 +383,7 @@ void TerrainDetailMapFeatGLSL::processVert( Vector &component void TerrainDetailMapFeatGLSL::processPix( Vector &componentList, const MaterialFeatureData &fd ) { - const U32 detailIndex = getProcessIndex(); + const S32 detailIndex = getProcessIndex(); Var *inTex = getVertTexCoord( "texCoord" ); MultiLine *meta = new MultiLine; @@ -616,7 +616,7 @@ TerrainMacroMapFeatGLSL::TerrainMacroMapFeatGLSL() void TerrainMacroMapFeatGLSL::processVert( Vector &componentList, const MaterialFeatureData &fd ) { - const U32 detailIndex = getProcessIndex(); + const S32 detailIndex = getProcessIndex(); // Grab incoming texture coords... the base map feature // made sure this was created. @@ -674,7 +674,7 @@ void TerrainMacroMapFeatGLSL::processVert( Vector &componentL void TerrainMacroMapFeatGLSL::processPix( Vector &componentList, const MaterialFeatureData &fd ) { - const U32 detailIndex = getProcessIndex(); + const S32 detailIndex = getProcessIndex(); Var *inTex = getVertTexCoord( "texCoord" ); MultiLine *meta = new MultiLine; @@ -900,7 +900,7 @@ void TerrainNormalMapFeatGLSL::processPix( Vector &component meta->addStatement( new GenOp( " @ = tGetMatrix3Row(@, 2);\r\n", new DecOp( gbNormal ), viewToTangent ) ); } - const U32 normalIndex = getProcessIndex(); + const S32 normalIndex = getProcessIndex(); Var *detailBlend = (Var*)LangElement::find( String::ToString( "detailBlend%d", normalIndex ) ); AssertFatal( detailBlend, "The detail blend is missing!" ); From 494922d9ed066f0a8117dacceb7ca3ca4fb56924 Mon Sep 17 00:00:00 2001 From: Anis Date: Sun, 21 Feb 2016 18:14:41 +0100 Subject: [PATCH 090/109] fixed the not working text edit RGB field on color picker. --- Engine/source/console/consoleFunctions.cpp | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/Engine/source/console/consoleFunctions.cpp b/Engine/source/console/consoleFunctions.cpp index 1e72f05b8..3a79a99e1 100644 --- a/Engine/source/console/consoleFunctions.cpp +++ b/Engine/source/console/consoleFunctions.cpp @@ -879,9 +879,11 @@ DefineConsoleFunction(ColorHEXToRGB, ColorI, (const char* hex), , "@endtsexample\n" "@ingroup Strings") { - ColorI color; - color.set(hex); - return color; + S32 rgb = dAtoui(hex, 16); + + ColorI color; + color.set(rgb & 0x000000FF, (rgb & 0x0000FF00) >> 8, (rgb & 0x00FF0000) >> 16); + return color; } DefineConsoleFunction(ColorHSBToRGB, ColorI, (Point3I hsb), , From aed2e0b5b63d5dfc46a23fb692b712910bae32ae Mon Sep 17 00:00:00 2001 From: Anis Date: Sun, 21 Feb 2016 22:09:14 +0100 Subject: [PATCH 091/109] Update guiColorPicker.cpp --- Engine/source/gui/controls/guiColorPicker.cpp | 480 +++++++++--------- 1 file changed, 228 insertions(+), 252 deletions(-) diff --git a/Engine/source/gui/controls/guiColorPicker.cpp b/Engine/source/gui/controls/guiColorPicker.cpp index 39c140434..97a8470d8 100644 --- a/Engine/source/gui/controls/guiColorPicker.cpp +++ b/Engine/source/gui/controls/guiColorPicker.cpp @@ -39,13 +39,13 @@ ColorF colorAlpha(0.0f, 0.0f, 0.0f, 0.0f); ColorF colorAlphaW(1.0f, 1.0f, 1.0f, 0.0f); ColorI GuiColorPickerCtrl::mColorRange[7] = { - ColorI(255,0,0), // Red - ColorI(255,0,255), // Pink - ColorI(0,0,255), // Blue - ColorI(0,255,255), // Light blue - ColorI(0,255,0), // Green - ColorI(255,255,0), // Yellow - ColorI(255,0,0), // Red + ColorI(255,0,0), // Red + ColorI(255,0,255), // Pink + ColorI(0,0,255), // Blue + ColorI(0,255,255), // Light blue + ColorI(0,255,0), // Green + ColorI(255,255,0), // Yellow + ColorI(255,0,0), // Red }; /// @} @@ -57,7 +57,6 @@ ConsoleDocClass( GuiColorPickerCtrl, "@internal" ); -//-------------------------------------------------------------------------- GuiColorPickerCtrl::GuiColorPickerCtrl() { setExtent(140, 30); @@ -70,56 +69,50 @@ GuiColorPickerCtrl::GuiColorPickerCtrl() mPositionChanged = false; mSelectorGap = 1; mActionOnMove = false; - mShowReticle = true; - mSelectColor = false; - mSetColor = mSetColor.BLACK; - mBitmap = NULL; + mShowReticle = true; + mSelectColor = false; + mSetColor = mSetColor.BLACK; + mBitmap = NULL; } GuiColorPickerCtrl::~GuiColorPickerCtrl() { - if (mBitmap) - { - delete mBitmap; - mBitmap = NULL; - } + if (mBitmap) + { + delete mBitmap; + mBitmap = NULL; + } } -//-------------------------------------------------------------------------- - ImplementEnumType( GuiColorPickMode, "\n\n" "@ingroup GuiUtil" "@internal" ) - { GuiColorPickerCtrl::pPallet, "Pallete" }, - { GuiColorPickerCtrl::pHorizColorRange, "HorizColor"}, - { GuiColorPickerCtrl::pVertColorRange, "VertColor" }, - { GuiColorPickerCtrl::pHorizColorBrightnessRange, "HorizBrightnessColor"}, - { GuiColorPickerCtrl::pVertColorBrightnessRange, "VertBrightnessColor" }, - { GuiColorPickerCtrl::pBlendColorRange, "BlendColor"}, - { GuiColorPickerCtrl::pHorizAlphaRange, "HorizAlpha"}, - { GuiColorPickerCtrl::pVertAlphaRange, "VertAlpha" }, - { GuiColorPickerCtrl::pDropperBackground, "Dropper" }, + { GuiColorPickerCtrl::pPallet, "Pallete" }, + { GuiColorPickerCtrl::pHorizColorRange, "HorizColor"}, + { GuiColorPickerCtrl::pVertColorRange, "VertColor" }, + { GuiColorPickerCtrl::pHorizColorBrightnessRange, "HorizBrightnessColor" }, + { GuiColorPickerCtrl::pVertColorBrightnessRange, "VertBrightnessColor" }, + { GuiColorPickerCtrl::pBlendColorRange, "BlendColor" }, + { GuiColorPickerCtrl::pHorizAlphaRange, "HorizAlpha" }, + { GuiColorPickerCtrl::pVertAlphaRange, "VertAlpha" }, + { GuiColorPickerCtrl::pDropperBackground, "Dropper" }, EndImplementEnumType; -//-------------------------------------------------------------------------- void GuiColorPickerCtrl::initPersistFields() { addGroup("ColorPicker"); - addField("baseColor", TypeColorF, Offset(mBaseColor, GuiColorPickerCtrl)); addField("pickColor", TypeColorF, Offset(mPickColor, GuiColorPickerCtrl)); addField("selectorGap", TypeS32, Offset(mSelectorGap, GuiColorPickerCtrl)); addField("displayMode", TYPEID< PickMode >(), Offset(mDisplayMode, GuiColorPickerCtrl) ); addField("actionOnMove", TypeBool,Offset(mActionOnMove, GuiColorPickerCtrl)); addField("showReticle", TypeBool, Offset(mShowReticle, GuiColorPickerCtrl)); - endGroup("ColorPicker"); Parent::initPersistFields(); } -//-------------------------------------------------------------------------- // Function to draw a box which can have 4 different colors in each corner blended together void GuiColorPickerCtrl::drawBlendBox(RectI &bounds, ColorF &c1, ColorF &c2, ColorF &c3, ColorF &c4) { @@ -131,54 +124,54 @@ void GuiColorPickerCtrl::drawBlendBox(RectI &bounds, ColorF &c1, ColorF &c2, Col //A couple of checks to determine if color blend if(c1 == colorWhite && c3 == colorAlpha && c4 == colorBlack) { - //Color - PrimBuild::begin( GFXTriangleFan, 4 ); - PrimBuild::color( c2 ); - PrimBuild::vertex2i( r, t ); + //Color + PrimBuild::begin(GFXTriangleFan, 4); + PrimBuild::color( c2 ); + PrimBuild::vertex2i( r, t ); - PrimBuild::color( c2 ); - PrimBuild::vertex2i( r, b ); + PrimBuild::color( c2 ); + PrimBuild::vertex2i( r, b ); - PrimBuild::color( c2 ); - PrimBuild::vertex2i( l, b ); + PrimBuild::color( c2 ); + PrimBuild::vertex2i( l, b ); - PrimBuild::color( c2 ); - PrimBuild::vertex2i( l, t ); - PrimBuild::end(); + PrimBuild::color( c2 ); + PrimBuild::vertex2i( l, t ); + PrimBuild::end(); - //White - PrimBuild::begin( GFXTriangleFan, 4 ); - PrimBuild::color( colorAlphaW ); - PrimBuild::vertex2i( r, t ); + //White + PrimBuild::begin( GFXTriangleFan, 4 ); + PrimBuild::color( colorAlphaW ); + PrimBuild::vertex2i( r, t ); - PrimBuild::color( colorAlphaW ); - PrimBuild::vertex2i( r, b ); + PrimBuild::color( colorAlphaW ); + PrimBuild::vertex2i( r, b ); - PrimBuild::color( c1 ); - PrimBuild::vertex2i( l, b ); + PrimBuild::color( c1 ); + PrimBuild::vertex2i( l, b ); - PrimBuild::color( c1 ); - PrimBuild::vertex2i( l, t ); - PrimBuild::end(); + PrimBuild::color( c1 ); + PrimBuild::vertex2i( l, t ); + PrimBuild::end(); - //Black - PrimBuild::begin( GFXTriangleFan, 4 ); - PrimBuild::color( c3 ); - PrimBuild::vertex2i( r, t ); + //Black + PrimBuild::begin( GFXTriangleFan, 4 ); + PrimBuild::color( c3 ); + PrimBuild::vertex2i( r, t ); - PrimBuild::color( c4 ); - PrimBuild::vertex2i( r, b ); + PrimBuild::color( c4 ); + PrimBuild::vertex2i( r, b ); - PrimBuild::color( c4 ); - PrimBuild::vertex2i( l, b ); + PrimBuild::color( c4 ); + PrimBuild::vertex2i( l, b ); - PrimBuild::color( c3 ); - PrimBuild::vertex2i( l, t ); - PrimBuild::end(); + PrimBuild::color( c3 ); + PrimBuild::vertex2i( l, t ); + PrimBuild::end(); } else { - PrimBuild::begin( GFXTriangleFan, 4 ); + PrimBuild::begin( GFXTriangleFan, 4 ); PrimBuild::color( c1 ); PrimBuild::vertex2i( l, t ); @@ -245,31 +238,29 @@ void GuiColorPickerCtrl::drawBlendRangeBox(RectI &bounds, bool vertical, U8 numC void GuiColorPickerCtrl::drawSelector(RectI &bounds, Point2I &selectorPos, SelectorMode mode) { - if( !mShowReticle ) - return; + if( !mShowReticle ) + return; - U16 sMax = mSelectorGap*2; - switch (mode) - { - case sVertical: - // Now draw the vertical selector - // Up -> Pos - if (selectorPos.y != bounds.point.y+1) - GFX->getDrawUtil()->drawLine(selectorPos.x, bounds.point.y, selectorPos.x, selectorPos.y-sMax-1, colorWhiteBlend); - // Down -> Pos - if (selectorPos.y != bounds.point.y+bounds.extent.y) - GFX->getDrawUtil()->drawLine(selectorPos.x, selectorPos.y + sMax, selectorPos.x, bounds.point.y + bounds.extent.y, colorWhiteBlend); - break; - case sHorizontal: - // Now draw the horizontal selector - // Left -> Pos - if (selectorPos.x != bounds.point.x) + U16 sMax = mSelectorGap*2; + switch (mode) + { + case sVertical: + // Now draw the vertical selector Up -> Pos + if (selectorPos.y != bounds.point.y+1) + GFX->getDrawUtil()->drawLine(selectorPos.x, bounds.point.y, selectorPos.x, selectorPos.y-sMax-1, colorWhiteBlend); + // Down -> Pos + if (selectorPos.y != bounds.point.y+bounds.extent.y) + GFX->getDrawUtil()->drawLine(selectorPos.x, selectorPos.y + sMax, selectorPos.x, bounds.point.y + bounds.extent.y, colorWhiteBlend); + break; + case sHorizontal: + // Now draw the horizontal selector Left -> Pos + if (selectorPos.x != bounds.point.x) GFX->getDrawUtil()->drawLine(bounds.point.x, selectorPos.y-1, selectorPos.x-sMax, selectorPos.y-1, colorWhiteBlend); - // Right -> Pos - if (selectorPos.x != bounds.point.x) + // Right -> Pos + if (selectorPos.x != bounds.point.x) GFX->getDrawUtil()->drawLine(bounds.point.x+mSelectorPos.x+sMax, selectorPos.y-1, bounds.point.x + bounds.extent.x, selectorPos.y-1, colorWhiteBlend); - break; - } + break; + } } //-------------------------------------------------------------------------- @@ -281,10 +272,10 @@ void GuiColorPickerCtrl::renderColorBox(RectI &bounds) pickerBounds.point.y = bounds.point.y+1; pickerBounds.extent.x = bounds.extent.x-1; pickerBounds.extent.y = bounds.extent.y-1; - + if (mProfile->mBorder) GFX->getDrawUtil()->drawRect(bounds, mProfile->mBorderColor); - + Point2I selectorPos = Point2I(bounds.point.x+mSelectorPos.x+1, bounds.point.y+mSelectorPos.y+1); // Draw color box differently depending on mode @@ -343,183 +334,176 @@ void GuiColorPickerCtrl::renderColorBox(RectI &bounds) void GuiColorPickerCtrl::onRender(Point2I offset, const RectI& updateRect) { - if (mStateBlock.isNull()) - { - GFXStateBlockDesc desc; - desc.setBlend(true, GFXBlendSrcAlpha, GFXBlendInvSrcAlpha); - desc.setZReadWrite(false); - desc.zWriteEnable = false; - desc.setCullMode(GFXCullNone); - mStateBlock = GFX->createStateBlock(desc); - } + if (mStateBlock.isNull()) + { + GFXStateBlockDesc desc; + desc.setBlend(true, GFXBlendSrcAlpha, GFXBlendInvSrcAlpha); + desc.setZReadWrite(false); + desc.zWriteEnable = false; + desc.setCullMode(GFXCullNone); + mStateBlock = GFX->createStateBlock(desc); + } - RectI boundsRect(offset, getExtent()); - renderColorBox(boundsRect); + RectI boundsRect(offset, getExtent()); + renderColorBox(boundsRect); - if (mPositionChanged || mBitmap == NULL) - { - bool nullBitmap = false; + if (mPositionChanged || mBitmap == NULL) + { + bool nullBitmap = false; - if (mPositionChanged == false && mBitmap == NULL) - nullBitmap = true; + if (mPositionChanged == false && mBitmap == NULL) + nullBitmap = true; - mPositionChanged = false; - Point2I extent = getRoot()->getExtent(); - // If we are anything but a pallete, change the pick color - if (mDisplayMode != pPallet) - { - Point2I resolution = getRoot()->getExtent(); + mPositionChanged = false; + Point2I extent = getRoot()->getExtent(); - U32 buf_x = offset.x + mSelectorPos.x + 1; - U32 buf_y = resolution.y - (extent.y - (offset.y + mSelectorPos.y + 1)); + // If we are anything but a pallete, change the pick color + if (mDisplayMode != pPallet) + { + Point2I resolution = getRoot()->getExtent(); - GFXTexHandle bb(resolution.x, - resolution.y, - GFXFormatR8G8B8A8, &GFXDefaultRenderTargetProfile, avar("%s() - bb (line %d)", __FUNCTION__, __LINE__)); + U32 buf_x = offset.x + mSelectorPos.x + 1; + U32 buf_y = resolution.y - (extent.y - (offset.y + mSelectorPos.y + 1)); - Point2I tmpPt(buf_x, buf_y); + GFXTexHandle bb( resolution.x, resolution.y, GFXFormatR8G8B8A8, &GFXDefaultRenderTargetProfile, avar("%s() - bb (line %d)", __FUNCTION__, __LINE__) ); - GFXTarget *targ = GFX->getActiveRenderTarget(); - targ->resolveTo(bb); + Point2I tmpPt(buf_x, buf_y); - if (mBitmap) - { - delete mBitmap; - mBitmap = NULL; - } + GFXTarget *targ = GFX->getActiveRenderTarget(); + targ->resolveTo(bb); - mBitmap = new GBitmap(bb.getWidth(), bb.getHeight()); + if (mBitmap) + { + delete mBitmap; + mBitmap = NULL; + } - bb.copyToBmp(mBitmap); + mBitmap = new GBitmap(bb.getWidth(), bb.getHeight()); - //bmp.writePNGDebug( "foo.png" ); + bb.copyToBmp(mBitmap); - if (!nullBitmap) - { - if (mSelectColor) - { - Point2I pos = findColor(mSetColor, offset, resolution, *mBitmap); - mSetColor = mSetColor.BLACK; - mSelectColor = false; + if (!nullBitmap) + { + if (mSelectColor) + { + Point2I pos = findColor(mSetColor, offset, resolution, *mBitmap); + mSetColor = mSetColor.BLACK; + mSelectColor = false; + setSelectorPos(pos); + } + else + { + ColorI tmp; + mBitmap->getColor(buf_x, buf_y, tmp); - setSelectorPos(pos); - } - else - { - ColorI tmp; - mBitmap->getColor(buf_x, buf_y, tmp); + mPickColor = (ColorF)tmp; - mPickColor = (ColorF)tmp; + // Now do onAction() if we are allowed + if (mActionOnMove) + onAction(); + } + } + } + } - // Now do onAction() if we are allowed - if (mActionOnMove) - onAction(); - } - } - } - - } - - //render the children - renderChildControls(offset, updateRect); + //render the children + renderChildControls(offset, updateRect); } void GuiColorPickerCtrl::setSelectorPos(const ColorF & color) { - if (mBitmap && !mPositionChanged) - { - Point2I resolution = getRoot() ? getRoot()->getExtent() : Point2I(1024, 768); - RectI rect(getGlobalBounds()); - Point2I pos = findColor(color, rect.point, resolution, *mBitmap); - mSetColor = mSetColor.BLACK; - mSelectColor = false; + if (mBitmap && !mPositionChanged) + { + Point2I resolution = getRoot() ? getRoot()->getExtent() : Point2I(1024, 768); + RectI rect(getGlobalBounds()); + Point2I pos = findColor(color, rect.point, resolution, *mBitmap); + mSetColor = mSetColor.BLACK; + mSelectColor = false; - setSelectorPos(pos); - } - else - { - mSetColor = color; - mSelectColor = true; - mPositionChanged = true; - } + setSelectorPos(pos); + } + else + { + mSetColor = color; + mSelectColor = true; + mPositionChanged = true; + } } Point2I GuiColorPickerCtrl::findColor(const ColorF & color, const Point2I& offset, const Point2I& resolution, GBitmap& bmp) { - RectI rect; - Point2I ext = getExtent(); - if (mDisplayMode != pDropperBackground) - { - ext.x -= 3; - ext.y -= 2; - rect = RectI(Point2I(1, 1), ext); - } - else - { - rect = RectI(Point2I(0, 0), ext); - } + RectI rect; + Point2I ext = getExtent(); + if (mDisplayMode != pDropperBackground) + { + ext.x -= 3; + ext.y -= 2; + rect = RectI(Point2I(1, 1), ext); + } + else + { + rect = RectI(Point2I(0, 0), ext); + } - Point2I closestPos(-1, -1); + Point2I closestPos(-1, -1); - /* Debugging - char filename[256]; - dSprintf( filename, 256, "%s.%s", "colorPickerTest", "png" ); + /* Debugging + char filename[256]; + dSprintf( filename, 256, "%s.%s", "colorPickerTest", "png" ); - // Open up the file on disk. - FileStream fs; - if ( !fs.open( filename, Torque::FS::File::Write ) ) - Con::errorf( "GuiObjectView::saveAsImage() - Failed to open output file '%s'!", filename ); - else - { - // Write it and close. - bmp.writeBitmap( "png", fs ); + // Open up the file on disk. + FileStream fs; + if ( !fs.open( filename, Torque::FS::File::Write ) ) + Con::errorf( "GuiObjectView::saveAsImage() - Failed to open output file '%s'!", filename ); + else + { + // Write it and close. + bmp.writeBitmap( "png", fs ); - fs.close(); - } - */ + fs.close(); + } + */ - ColorI tmp; - U32 buf_x; - U32 buf_y; - ColorF curColor; - F32 val(10000.0f); - F32 closestVal(10000.0f); - bool closestSet = false; + ColorI tmp; + U32 buf_x; + U32 buf_y; + ColorF curColor; + F32 val(10000.0f); + F32 closestVal(10000.0f); + bool closestSet = false; - for (S32 x = rect.point.x; x <= rect.extent.x; x++) - { - for (S32 y = rect.point.y; y <= rect.extent.y; y++) - { - buf_x = offset.x + x + 1; - buf_y = (resolution.y - (offset.y + y + 1)); - if (GFX->getAdapterType() != OpenGL) - buf_y = resolution.y - buf_y; + for (S32 x = rect.point.x; x <= rect.extent.x; x++) + { + for (S32 y = rect.point.y; y <= rect.extent.y; y++) + { + buf_x = offset.x + x + 1; + buf_y = (resolution.y - (offset.y + y + 1)); + buf_y = resolution.y - buf_y; - //Get the color at that position - bmp.getColor(buf_x, buf_y, tmp); - curColor = (ColorF)tmp; + //Get the color at that position + bmp.getColor(buf_x, buf_y, tmp); + curColor = (ColorF)tmp; - //Evaluate how close the color is to our desired color - val = mFabs(color.red - curColor.red) + mFabs(color.green - curColor.green) + mFabs(color.blue - curColor.blue); + //Evaluate how close the color is to our desired color + val = mFabs(color.red - curColor.red) + mFabs(color.green - curColor.green) + mFabs(color.blue - curColor.blue); - if (!closestSet) - { - closestVal = val; - closestPos.set(x, y); - closestSet = true; - } - else if (val < closestVal) - { - closestVal = val; - closestPos.set(x, y); - } - } - } + if (!closestSet) + { + closestVal = val; + closestPos.set(x, y); + closestSet = true; + } + else if (val < closestVal) + { + closestVal = val; + closestPos.set(x, y); + } + } + } - return closestPos; + return closestPos; } -//-------------------------------------------------------------------------- void GuiColorPickerCtrl::setSelectorPos(const Point2I &pos) { Point2I extent = getExtent(); @@ -564,7 +548,6 @@ void GuiColorPickerCtrl::setSelectorPos(const Point2I &pos) } } -//-------------------------------------------------------------------------- void GuiColorPickerCtrl::onMouseDown(const GuiEvent &event) { if (!mActive) @@ -577,14 +560,14 @@ void GuiColorPickerCtrl::onMouseDown(const GuiEvent &event) if (mProfile->mCanKeyFocus) setFirstResponder(); - - if (mActive && (mDisplayMode != pDropperBackground)) + + if (mActive && (mDisplayMode != pDropperBackground)) onAction(); // Update the picker cross position if (mDisplayMode != pPallet) - setSelectorPos(globalToLocalCoord(event.mousePoint)); - + setSelectorPos(globalToLocalCoord(event.mousePoint)); + mMouseDown = true; } @@ -600,10 +583,8 @@ void GuiColorPickerCtrl::onMouseDragged(const GuiEvent &event) if( !mActionOnMove ) execAltConsoleCallback(); - } -//-------------------------------------------------------------------------- void GuiColorPickerCtrl::onMouseMove(const GuiEvent &event) { // Only for dropper mode @@ -611,45 +592,40 @@ void GuiColorPickerCtrl::onMouseMove(const GuiEvent &event) setSelectorPos(globalToLocalCoord(event.mousePoint)); } -//-------------------------------------------------------------------------- void GuiColorPickerCtrl::onMouseEnter(const GuiEvent &event) { mMouseOver = true; } -//-------------------------------------------------------------------------- void GuiColorPickerCtrl::onMouseLeave(const GuiEvent &) { // Reset state mMouseOver = false; } -//-------------------------------------------------------------------------- void GuiColorPickerCtrl::onMouseUp(const GuiEvent &) { //if we released the mouse within this control, perform the action - if (mActive && mMouseDown && (mDisplayMode != pDropperBackground)) + if (mActive && mMouseDown && (mDisplayMode != pDropperBackground)) mMouseDown = false; - if (mActive && (mDisplayMode == pDropperBackground)) + if (mActive && (mDisplayMode == pDropperBackground)) { // In a dropper, the alt command executes the mouse up action (to signal stopping) execAltConsoleCallback(); } - + mouseUnlock(); } -//-------------------------------------------------------------------------- const char *GuiColorPickerCtrl::getScriptValue() { static char temp[256]; ColorF color = getValue(); - dSprintf(temp,256,"%f %f %f %f",color.red, color.green, color.blue, color.alpha); - return temp; + dSprintf( temp, 256, "%f %f %f %f", color.red, color.green, color.blue, color.alpha ); + return temp; } -//-------------------------------------------------------------------------- void GuiColorPickerCtrl::setScriptValue(const char *value) { ColorF newValue; @@ -669,12 +645,12 @@ DefineConsoleMethod(GuiColorPickerCtrl, setSelectorPos, void, (Point2I newPos), DefineConsoleMethod(GuiColorPickerCtrl, updateColor, void, (), , "Forces update of pick color") { - object->updateColor(); + object->updateColor(); } DefineEngineMethod(GuiColorPickerCtrl, setSelectorColor, void, (ColorF color), , - "Sets the current position of the selector based on a color.n" - "@param color Color to look for.n") + "Sets the current position of the selector based on a color.n" + "@param color Color to look for.n") { - object->setSelectorPos(color); -} \ No newline at end of file + object->setSelectorPos(color); +} From 5c2bfbf82eaec500f2e2b7138a6697d762205240 Mon Sep 17 00:00:00 2001 From: Anis Date: Sun, 21 Feb 2016 22:10:17 +0100 Subject: [PATCH 092/109] Update guiColorPicker.h --- Engine/source/gui/controls/guiColorPicker.h | 37 ++++++++++----------- 1 file changed, 18 insertions(+), 19 deletions(-) diff --git a/Engine/source/gui/controls/guiColorPicker.h b/Engine/source/gui/controls/guiColorPicker.h index 202a2b5ca..42531c3bb 100644 --- a/Engine/source/gui/controls/guiColorPicker.h +++ b/Engine/source/gui/controls/guiColorPicker.h @@ -59,29 +59,28 @@ class GuiColorPickerCtrl : public GuiControl public: enum PickMode { - pPallet = 0, ///< We just have a solid color; We just act like a pallet - pHorizColorRange, ///< We have a range of base colors going horizontally - pVertColorRange, ///< We have a range of base colors going vertically + pPallet = 0, ///< We just have a solid color; We just act like a pallet + pHorizColorRange, ///< We have a range of base colors going horizontally + pVertColorRange, ///< We have a range of base colors going vertically pHorizColorBrightnessRange, ///< HorizColorRange with brightness - pVertColorBrightnessRange, ///< VertColorRange with brightness - pBlendColorRange, ///< We have a box which shows a range in brightness of the color - pHorizAlphaRange, ///< We have a box which shows a range in alpha going horizontally - pVertAlphaRange, ///< We have a box which shows a range in alpha going vertically - pDropperBackground ///< The control does not draw anything; Only does something when you click, or move the mouse (when active) + pVertColorBrightnessRange, ///< VertColorRange with brightness + pBlendColorRange, ///< We have a box which shows a range in brightness of the color + pHorizAlphaRange, ///< We have a box which shows a range in alpha going horizontally + pVertAlphaRange, ///< We have a box which shows a range in alpha going vertically + pDropperBackground ///< The control does not draw anything; Only does something when you click, or move the mouse (when active) }; enum SelectorMode { - sHorizontal = 0, ///< Horizontal selector with small gap - sVertical, ///< Vertical selector with small gap + sHorizontal = 0, ///< Horizontal selector with small gap + sVertical, ///< Vertical selector with small gap }; - + protected: - /// @name Core Rendering functions /// @{ - void renderColorBox(RectI &bounds); ///< Function that draws the actual color box - void drawSelector(RectI &bounds, Point2I &selectorPos, SelectorMode mode); ///< Function that draws the selection indicator + void renderColorBox(RectI &bounds); ///< Function that draws the actual color box + void drawSelector(RectI &bounds, Point2I &selectorPos, SelectorMode mode); /// < Function that draws the selection indicator void drawBlendBox(RectI &bounds, ColorF &c1, ColorF &c2, ColorF &c3, ColorF &c4); void drawBlendRangeBox(RectI &bounds, bool vertical, U8 numColors, ColorI *colors); /// @} @@ -111,8 +110,8 @@ class GuiColorPickerCtrl : public GuiControl static ColorI mColorRange[7]; ///< Color range for pHorizColorRange and pVertColorRange /// @} - public: - + public: + DECLARE_CONOBJECT(GuiColorPickerCtrl); DECLARE_CATEGORY( "Gui Editor" ); @@ -127,19 +126,19 @@ class GuiColorPickerCtrl : public GuiControl /// NOTE: setValue only sets baseColor, since setting pickColor wouldn't be useful void setValue(ColorF &value) {mBaseColor = value;} /// NOTE: getValue() returns baseColor if pallet (since pallet controls can't "pick" colours themselves) - ColorF getValue() {return mDisplayMode == pPallet ? mBaseColor : mPickColor;} + ColorF getValue() { return mDisplayMode == pPallet ? mBaseColor : mPickColor; } const char *getScriptValue(); void setScriptValue(const char *value); void updateColor() {mPositionChanged = true;} /// @} - + /// @name Selector Functions /// @{ void setSelectorPos(const Point2I &pos); ///< Set new pos (in local coords) void setSelectorPos(const ColorF & color); Point2I getSelectorPos() {return mSelectorPos;} /// @} - + /// @name Input Events /// @{ void onMouseDown(const GuiEvent &); From 8ec2e534dc1c617a0b350179d948adba539236f5 Mon Sep 17 00:00:00 2001 From: Anis Date: Sun, 21 Feb 2016 22:36:27 +0100 Subject: [PATCH 093/109] removed tabs --- Engine/source/console/consoleFunctions.cpp | 86 +++++++++++----------- 1 file changed, 43 insertions(+), 43 deletions(-) diff --git a/Engine/source/console/consoleFunctions.cpp b/Engine/source/console/consoleFunctions.cpp index 3a79a99e1..3e9466a16 100644 --- a/Engine/source/console/consoleFunctions.cpp +++ b/Engine/source/console/consoleFunctions.cpp @@ -833,51 +833,51 @@ DefineConsoleFunction(ColorFloatToInt, ColorI, (ColorF color), , } DefineConsoleFunction(ColorIntToFloat, ColorF, (ColorI color), , - "Convert from a integer color to an float color (0 to 255 to 0.0 - 1.0).\n" - "@param color Integer color value to be converted in the form \"R G B A\", where R is red, G is green, B is blue, and A is alpha.\n" - "@return Converted color value (0.0 - 1.0)\n\n" - "@tsexample\n" - "ColorIntToFloat( \"0 0 255 128\" ) // Returns \"0 0 1 0.5\".\n" - "@endtsexample\n" - "@ingroup Strings") + "Convert from a integer color to an float color (0 to 255 to 0.0 - 1.0).\n" + "@param color Integer color value to be converted in the form \"R G B A\", where R is red, G is green, B is blue, and A is alpha.\n" + "@return Converted color value (0.0 - 1.0)\n\n" + "@tsexample\n" + "ColorIntToFloat( \"0 0 255 128\" ) // Returns \"0 0 1 0.5\".\n" + "@endtsexample\n" + "@ingroup Strings") { - return (ColorF)color; + return (ColorF)color; } DefineConsoleFunction(ColorRGBToHEX, const char*, (ColorI color), , - "Convert from a integer RGB (red, green, blue) color to hex color value (0 to 255 to 00 - FF).\n" - "@param color Integer color value to be converted in the form \"R G B A\", where R is red, G is green, B is blue, and A is alpha. It excepts an alpha, but keep in mind this will not be converted.\n" - "@return Hex color value (#000000 - #FFFFFF), alpha isn't handled/converted so it is only the RGB value\n\n" - "@tsexample\n" - "ColorRBGToHEX( \"0 0 255 128\" ) // Returns \"#0000FF\".\n" - "@endtsexample\n" - "@ingroup Strings") + "Convert from a integer RGB (red, green, blue) color to hex color value (0 to 255 to 00 - FF).\n" + "@param color Integer color value to be converted in the form \"R G B A\", where R is red, G is green, B is blue, and A is alpha. It excepts an alpha, but keep in mind this will not be converted.\n" + "@return Hex color value (#000000 - #FFFFFF), alpha isn't handled/converted so it is only the RGB value\n\n" + "@tsexample\n" + "ColorRBGToHEX( \"0 0 255 128\" ) // Returns \"#0000FF\".\n" + "@endtsexample\n" + "@ingroup Strings") { - return Con::getReturnBuffer(color.getHex()); + return Con::getReturnBuffer(color.getHex()); } DefineConsoleFunction(ColorRGBToHSB, const char*, (ColorI color), , - "Convert from a integer RGB (red, green, blue) color to HSB (hue, saturation, brightness). HSB is also know as HSL or HSV as well, with the last letter standing for lightness or value.\n" - "@param color Integer color value to be converted in the form \"R G B A\", where R is red, G is green, B is blue, and A is alpha. It excepts an alpha, but keep in mind this will not be converted.\n" - "@return HSB color value, alpha isn't handled/converted so it is only the RGB value\n\n" - "@tsexample\n" - "ColorRBGToHSB( \"0 0 255 128\" ) // Returns \"240 100 100\".\n" - "@endtsexample\n" - "@ingroup Strings") + "Convert from a integer RGB (red, green, blue) color to HSB (hue, saturation, brightness). HSB is also know as HSL or HSV as well, with the last letter standing for lightness or value.\n" + "@param color Integer color value to be converted in the form \"R G B A\", where R is red, G is green, B is blue, and A is alpha. It excepts an alpha, but keep in mind this will not be converted.\n" + "@return HSB color value, alpha isn't handled/converted so it is only the RGB value\n\n" + "@tsexample\n" + "ColorRBGToHSB( \"0 0 255 128\" ) // Returns \"240 100 100\".\n" + "@endtsexample\n" + "@ingroup Strings") { - ColorI::Hsb hsb(color.getHSB()); - String s(String::ToString(hsb.hue) + " " + String::ToString(hsb.sat) + " " + String::ToString(hsb.brightness)); - return Con::getReturnBuffer(s); + ColorI::Hsb hsb(color.getHSB()); + String s(String::ToString(hsb.hue) + " " + String::ToString(hsb.sat) + " " + String::ToString(hsb.brightness)); + return Con::getReturnBuffer(s); } DefineConsoleFunction(ColorHEXToRGB, ColorI, (const char* hex), , - "Convert from a hex color value to an integer RGB (red, green, blue) color (00 - FF to 0 to 255).\n" - "@param hex Hex color value (#000000 - #FFFFFF) to be converted to an RGB (red, green, blue) value.\n" - "@return Integer color value to be converted in the form \"R G B A\", where R is red, G is green, B is blue, and A is alpha. Alpha isn't handled/converted so only pay attention to the RGB value\n\n" - "@tsexample\n" - "ColorHEXToRGB( \"#0000FF\" ) // Returns \"0 0 255 0\".\n" - "@endtsexample\n" - "@ingroup Strings") + "Convert from a hex color value to an integer RGB (red, green, blue) color (00 - FF to 0 to 255).\n" + "@param hex Hex color value (#000000 - #FFFFFF) to be converted to an RGB (red, green, blue) value.\n" + "@return Integer color value to be converted in the form \"R G B A\", where R is red, G is green, B is blue, and A is alpha. Alpha isn't handled/converted so only pay attention to the RGB value\n\n" + "@tsexample\n" + "ColorHEXToRGB( \"#0000FF\" ) // Returns \"0 0 255 0\".\n" + "@endtsexample\n" + "@ingroup Strings") { S32 rgb = dAtoui(hex, 16); @@ -887,17 +887,17 @@ DefineConsoleFunction(ColorHEXToRGB, ColorI, (const char* hex), , } DefineConsoleFunction(ColorHSBToRGB, ColorI, (Point3I hsb), , - "Convert from a HSB (hue, saturation, brightness) to an integer RGB (red, green, blue) color. HSB is also know as HSL or HSV as well, with the last letter standing for lightness or value.\n" - "@param hsb HSB (hue, saturation, brightness) value to be converted.\n" - "@return Integer color value to be converted in the form \"R G B A\", where R is red, G is green, B is blue, and A is alpha. Alpha isn't handled/converted so only pay attention to the RGB value\n\n" - "@tsexample\n" - "ColorHSBToRGB( \"240 100 100\" ) // Returns \"0 0 255 0\".\n" - "@endtsexample\n" - "@ingroup Strings") + "Convert from a HSB (hue, saturation, brightness) to an integer RGB (red, green, blue) color. HSB is also know as HSL or HSV as well, with the last letter standing for lightness or value.\n" + "@param hsb HSB (hue, saturation, brightness) value to be converted.\n" + "@return Integer color value to be converted in the form \"R G B A\", where R is red, G is green, B is blue, and A is alpha. Alpha isn't handled/converted so only pay attention to the RGB value\n\n" + "@tsexample\n" + "ColorHSBToRGB( \"240 100 100\" ) // Returns \"0 0 255 0\".\n" + "@endtsexample\n" + "@ingroup Strings") { - ColorI color; - color.set(ColorI::Hsb(hsb.x, hsb.y, hsb.z)); - return color; + ColorI color; + color.set(ColorI::Hsb(hsb.x, hsb.y, hsb.z)); + return color; } //============================================================================= From 4c0d3bbc3492b699b2f6d11e6c0c7858fa2bed8d Mon Sep 17 00:00:00 2001 From: Anis Date: Sun, 21 Feb 2016 22:41:35 +0100 Subject: [PATCH 094/109] removed tabs --- Engine/source/core/color.h | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/Engine/source/core/color.h b/Engine/source/core/color.h index ddb98c8a8..63e69921b 100644 --- a/Engine/source/core/color.h +++ b/Engine/source/core/color.h @@ -127,12 +127,12 @@ class ColorI struct Hsb { - Hsb() :hue(0), sat(0), brightness(0){}; - Hsb(U32 h, U32 s, U32 b) :hue(h), sat(s), brightness(b){}; + Hsb() :hue(0), sat(0), brightness(0){}; + Hsb(U32 h, U32 s, U32 b) :hue(h), sat(s), brightness(b){}; - U32 hue; ///Hue - U32 sat; ///Saturation - U32 brightness; //Brightness/Value/Lightness + U32 hue; ///Hue + U32 sat; ///Saturation + U32 brightness; //Brightness/Value/Lightness }; public: From 2a1f81d3aaaab268f2129d69da274273a4f3c185 Mon Sep 17 00:00:00 2001 From: Anis Date: Sun, 21 Feb 2016 22:48:10 +0100 Subject: [PATCH 095/109] removed tabs --- Engine/source/math/mConsoleFunctions.cpp | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/Engine/source/math/mConsoleFunctions.cpp b/Engine/source/math/mConsoleFunctions.cpp index df380da1b..1d57743e6 100644 --- a/Engine/source/math/mConsoleFunctions.cpp +++ b/Engine/source/math/mConsoleFunctions.cpp @@ -104,16 +104,16 @@ DefineConsoleFunction( mRound, S32, ( F32 v ),, } DefineConsoleFunction( mRoundColour, F32, ( F32 v, S32 n ), (0), - "Round v to the nth decimal place or the nearest whole number by default." - "@param v Value to roundn" - "@param n Number of decimal places to round to, 0 by defaultn" - "@return The rounded value as a S32." - "@ingroup Math") + "Round v to the nth decimal place or the nearest whole number by default." + "@param v Value to roundn" + "@param n Number of decimal places to round to, 0 by defaultn" + "@return The rounded value as a S32." + "@ingroup Math") { - if (n <= 0) - return mRound(v); - else - return mRound(v, n); + if (n <= 0) + return mRound(v); + else + return mRound(v, n); } DefineConsoleFunction( mCeil, S32, ( F32 v ),, From 304c33e525947c4aebd72d2bb515a2584817a76d Mon Sep 17 00:00:00 2001 From: Anis Date: Sun, 21 Feb 2016 22:55:45 +0100 Subject: [PATCH 096/109] removed tabs --- .../source/gui/controls/guiTextEditCtrl.cpp | 34 +++++++++---------- 1 file changed, 17 insertions(+), 17 deletions(-) diff --git a/Engine/source/gui/controls/guiTextEditCtrl.cpp b/Engine/source/gui/controls/guiTextEditCtrl.cpp index 6c1935bb1..0cdf7ade6 100644 --- a/Engine/source/gui/controls/guiTextEditCtrl.cpp +++ b/Engine/source/gui/controls/guiTextEditCtrl.cpp @@ -1252,27 +1252,27 @@ void GuiTextEditCtrl::onLoseFirstResponder() Parent::onLoseFirstResponder(); } -void GuiTextEditCtrl::onRender(Point2I offset, const RectI &updateRect) +void GuiTextEditCtrl::onRender( Point2I offset, const RectI &updateRect ) { RectI ctrlRect( offset, getExtent() ); //if opaque, fill the update rect with the fill color if ( mProfile->mOpaque ) { - if (!mTextValid) - GFX->getDrawUtil()->drawRectFill(ctrlRect, mProfile->mFillColorERR); - else if (isFirstResponder()) - GFX->getDrawUtil()->drawRectFill(ctrlRect, mProfile->mFillColorHL); - else - GFX->getDrawUtil()->drawRectFill(ctrlRect, mProfile->mFillColor); + if ( !mTextValid ) + GFX->getDrawUtil()->drawRectFill( ctrlRect, mProfile->mFillColorERR ); + else if ( isFirstResponder() ) + GFX->getDrawUtil()->drawRectFill( ctrlRect, mProfile->mFillColorHL ); + else + GFX->getDrawUtil()->drawRectFill( ctrlRect, mProfile->mFillColor ); } //if there's a border, draw the border - if (mProfile->mBorder) + if ( mProfile->mBorder ) { - renderBorder(ctrlRect, mProfile); - if (!mTextValid) - GFX->getDrawUtil()->drawRectFill(ctrlRect, mProfile->mFillColorERR); + renderBorder( ctrlRect, mProfile ); + if ( !mTextValid ) + GFX->getDrawUtil()->drawRectFill( ctrlRect, mProfile->mFillColorERR ); } drawText( ctrlRect, isFirstResponder() ); @@ -1496,25 +1496,25 @@ void GuiTextEditCtrl::drawText( const RectI &drawRect, bool isFocused ) bool GuiTextEditCtrl::hasText() { - return (mTextBuffer.length()); + return ( mTextBuffer.length() ); } void GuiTextEditCtrl::invalidText(bool playSound) { - mTextValid = false; + mTextValid = false; - if (playSound) - playDeniedSound(); + if ( playSound ) + playDeniedSound(); } void GuiTextEditCtrl::validText() { - mTextValid = true; + mTextValid = true; } bool GuiTextEditCtrl::isValidText() { - return mTextValid; + return mTextValid; } void GuiTextEditCtrl::playDeniedSound() From c3b1aaae5a09837871bb7e88e836cc534acd70c6 Mon Sep 17 00:00:00 2001 From: irei1as Date: Tue, 23 Feb 2016 17:17:54 +0100 Subject: [PATCH 097/109] New script function to edit created decals TorqueScript function to edit decals created with decalManagerAddDecal. --- Engine/source/T3D/decal/decalManager.cpp | 41 ++++++++++++++++++++++++ 1 file changed, 41 insertions(+) diff --git a/Engine/source/T3D/decal/decalManager.cpp b/Engine/source/T3D/decal/decalManager.cpp index 6db06a521..d733a78dd 100644 --- a/Engine/source/T3D/decal/decalManager.cpp +++ b/Engine/source/T3D/decal/decalManager.cpp @@ -1744,3 +1744,44 @@ DefineEngineFunction( decalManagerRemoveDecal, bool, ( S32 decalID ),, gDecalManager->removeDecal(inst); return true; } + +DefineEngineFunction( decalManagerEditDecal, bool, ( S32 decalID, Point3F pos, Point3F normal, F32 rotAroundNormal, F32 decalScale ),, + "Edit specified decal of the decal manager.\n" + "@param decalID ID of the decal to edit.\n" + "@param pos World position for the decal.\n" + "@param normal Decal normal vector (if the decal was a tire lying flat on a " + "surface, this is the vector pointing in the direction of the axle).\n" + "@param rotAroundNormal Angle (in radians) to rotate this decal around its normal vector.\n" + "@param decalScale Scale factor applied to the decal.\n" + "@return Returns true if successful, false if decalID not found.\n" + "" ) +{ + DecalInstance *decalInstance = gDecalManager->getDecal( decalID ); + if( !decalInstance ) + return false; + + //Internally we need Point3F tangent instead of the user friendly F32 rotAroundNormal + MatrixF mat( true ); + MathUtils::getMatrixFromUpVector( normal, &mat ); + + AngAxisF rot( normal, rotAroundNormal ); + MatrixF rotmat; + rot.setMatrix( &rotmat ); + mat.mul( rotmat ); + + Point3F tangent; + mat.getColumn( 1, &tangent ); + + //if everything is unchanged just do nothing and return "everything is ok" + if ( pos.equal(decalInstance->mPosition) && normal.equal(decalInstance->mNormal) && tangent.equal(decalInstance->mTangent) && mFabs( decalInstance->mSize - (decalInstance->mDataBlock->size * decalScale) ) < POINT_EPSILON ) return true; + + decalInstance->mPosition = pos; + decalInstance->mNormal = normal; + decalInstance->mTangent = tangent; + decalInstance->mSize = decalInstance->mDataBlock->size * decalScale; + + gDecalManager->clipDecal( decalInstance, NULL, NULL); + + gDecalManager->notifyDecalModified( decalInstance ); + return true; +} From 988bd47d5279d36b11f30c8bc815d9361b6dcd68 Mon Sep 17 00:00:00 2001 From: irei1as Date: Wed, 24 Feb 2016 09:02:07 +0100 Subject: [PATCH 098/109] Visibility change as sugested by dottools --- Engine/source/T3D/decal/decalManager.cpp | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/Engine/source/T3D/decal/decalManager.cpp b/Engine/source/T3D/decal/decalManager.cpp index d733a78dd..f992705d1 100644 --- a/Engine/source/T3D/decal/decalManager.cpp +++ b/Engine/source/T3D/decal/decalManager.cpp @@ -1773,7 +1773,11 @@ DefineEngineFunction( decalManagerEditDecal, bool, ( S32 decalID, Point3F pos, P mat.getColumn( 1, &tangent ); //if everything is unchanged just do nothing and return "everything is ok" - if ( pos.equal(decalInstance->mPosition) && normal.equal(decalInstance->mNormal) && tangent.equal(decalInstance->mTangent) && mFabs( decalInstance->mSize - (decalInstance->mDataBlock->size * decalScale) ) < POINT_EPSILON ) return true; + if ( pos.equal(decalInstance->mPosition) && + normal.equal(decalInstance->mNormal) && + tangent.equal(decalInstance->mTangent) && + mFabs( decalInstance->mSize - (decalInstance->mDataBlock->size * decalScale) ) < POINT_EPSILON ) + return true; decalInstance->mPosition = pos; decalInstance->mNormal = normal; From f39f7a80c8e50732d3daac9a718a7b22f842aac0 Mon Sep 17 00:00:00 2001 From: Anis Date: Thu, 25 Feb 2016 15:26:25 +0100 Subject: [PATCH 099/109] Update gfxGLDevice.cpp --- Engine/source/gfx/gl/gfxGLDevice.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Engine/source/gfx/gl/gfxGLDevice.cpp b/Engine/source/gfx/gl/gfxGLDevice.cpp index b6227faad..cb5ead912 100644 --- a/Engine/source/gfx/gl/gfxGLDevice.cpp +++ b/Engine/source/gfx/gl/gfxGLDevice.cpp @@ -356,7 +356,7 @@ GFXVertexBuffer *GFXGLDevice::allocVertexBuffer( U32 numVerts, const GFXVertexFormat *vertexFormat, U32 vertSize, GFXBufferType bufferType, - void* data = NULL ) + void* data ) { if(bufferType == GFXBufferTypeVolatile) return findVolatileVBO(numVerts, vertexFormat, vertSize); From 740c999c19f7353ba1ccf2344ddee5640c02cce9 Mon Sep 17 00:00:00 2001 From: Anis Date: Thu, 25 Feb 2016 15:26:52 +0100 Subject: [PATCH 100/109] compile fix --- Engine/source/gfx/gl/gfxGLDevice.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Engine/source/gfx/gl/gfxGLDevice.cpp b/Engine/source/gfx/gl/gfxGLDevice.cpp index cb5ead912..01bbd02ea 100644 --- a/Engine/source/gfx/gl/gfxGLDevice.cpp +++ b/Engine/source/gfx/gl/gfxGLDevice.cpp @@ -377,7 +377,8 @@ GFXVertexBuffer *GFXGLDevice::allocVertexBuffer( U32 numVerts, GFXPrimitiveBuffer *GFXGLDevice::allocPrimitiveBuffer( U32 numIndices, U32 numPrimitives, GFXBufferType bufferType, void* data ) { - GFXPrimitiveBuffer* buf + GFXPrimitiveBuffer* buf; + if(bufferType == GFXBufferTypeVolatile) { buf = findVolatilePBO(numIndices, numPrimitives); From 7418fbfbbdd2a7bebedd8f80a9a732de6544c036 Mon Sep 17 00:00:00 2001 From: Anis Date: Thu, 25 Feb 2016 18:26:15 +0100 Subject: [PATCH 101/109] fixed memory leak in proper way --- Engine/source/platform/profiler.cpp | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/Engine/source/platform/profiler.cpp b/Engine/source/platform/profiler.cpp index fb60453fc..c978b9ac2 100644 --- a/Engine/source/platform/profiler.cpp +++ b/Engine/source/platform/profiler.cpp @@ -214,12 +214,22 @@ Profiler::~Profiler() void Profiler::reset() { mEnabled = false; // in case we're in a profiler call. - while (mProfileList) + ProfilerData * head = mProfileList; + ProfilerData * curr = head; + + while ( curr ) { - free(mProfileList); - mProfileList = NULL; + head = curr->mNextProfilerData; + free( curr ); + + if ( head ) + curr = head; + else + curr = NULL; } + mProfileList = NULL; + for(ProfilerRootData *walk = ProfilerRootData::sRootList; walk; walk = walk->mNextRoot) { walk->mFirstProfilerData = 0; From 496b6a3ea853a2e4fa6f80ef36fd78624a1c5a74 Mon Sep 17 00:00:00 2001 From: Azaezel Date: Fri, 26 Feb 2016 00:21:01 -0600 Subject: [PATCH 102/109] truncation warning cleanups dx side (was also causing cornercase crashes gl side) --- Engine/source/shaderGen/GLSL/shaderFeatureGLSL.cpp | 4 ++-- Engine/source/shaderGen/HLSL/shaderFeatureHLSL.cpp | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/Engine/source/shaderGen/GLSL/shaderFeatureGLSL.cpp b/Engine/source/shaderGen/GLSL/shaderFeatureGLSL.cpp index ffd67b45d..cea0ca5db 100644 --- a/Engine/source/shaderGen/GLSL/shaderFeatureGLSL.cpp +++ b/Engine/source/shaderGen/GLSL/shaderFeatureGLSL.cpp @@ -365,7 +365,7 @@ Var* ShaderFeatureGLSL::getOutTexCoord( const char *name, // Statement allows for casting of different types which // eliminates vector truncation problems. - String statement = String::ToString( " @ = %s(tMul(@, @));\r\n", type ); + String statement = String::ToString( " @ = %s(tMul(@, @).xy);\r\n", type ); meta->addStatement( new GenOp( statement , texCoord, texMat, inTex ) ); } else @@ -813,7 +813,7 @@ Var* ShaderFeatureGLSL::addOutDetailTexCoord( Vector &compon texMat->constSortPos = cspPass; } - meta->addStatement( new GenOp( " @ = tMul(@, @) * @;\r\n", outTex, texMat, inTex, detScale ) ); + meta->addStatement( new GenOp( " @ = tMul(@, @).xy * @;\r\n", outTex, texMat, inTex, detScale ) ); } else { diff --git a/Engine/source/shaderGen/HLSL/shaderFeatureHLSL.cpp b/Engine/source/shaderGen/HLSL/shaderFeatureHLSL.cpp index 59264c3a9..0b40cfac3 100644 --- a/Engine/source/shaderGen/HLSL/shaderFeatureHLSL.cpp +++ b/Engine/source/shaderGen/HLSL/shaderFeatureHLSL.cpp @@ -365,7 +365,7 @@ Var* ShaderFeatureHLSL::getOutTexCoord( const char *name, // Statement allows for casting of different types which // eliminates vector truncation problems. - String statement = String::ToString( " @ = (%s)mul(@, @);\r\n", type ); + String statement = String::ToString( " @ = (%s)mul(@, @).xy;\r\n", type ); meta->addStatement( new GenOp( statement, texCoord, texMat, inTex ) ); } else @@ -811,7 +811,7 @@ Var* ShaderFeatureHLSL::addOutDetailTexCoord( Vector &compon texMat->constSortPos = cspPass; } - meta->addStatement( new GenOp( " @ = mul(@, @) * @;\r\n", outTex, texMat, inTex, detScale ) ); + meta->addStatement( new GenOp( " @ = mul(@, @).xy * @;\r\n", outTex, texMat, inTex, detScale ) ); } else { From f701228a37bdd24fe8ca22f6480498fd69ad37f7 Mon Sep 17 00:00:00 2001 From: "Anis A. Hireche" Date: Fri, 26 Feb 2016 15:53:20 +0100 Subject: [PATCH 103/109] Steve Acaster's Ai Poses --- Engine/source/T3D/aiPlayer.cpp | 40 +++++++++++++++++++++++++++++++++ Engine/source/T3D/aiPlayer.h | 4 +++- Engine/source/T3D/player.cpp | 35 +++++++++++++++++++---------- Engine/source/T3D/shapeBase.cpp | 1 + Engine/source/T3D/shapeBase.h | 1 + 5 files changed, 68 insertions(+), 13 deletions(-) diff --git a/Engine/source/T3D/aiPlayer.cpp b/Engine/source/T3D/aiPlayer.cpp index 8992c2c55..8b35e478e 100644 --- a/Engine/source/T3D/aiPlayer.cpp +++ b/Engine/source/T3D/aiPlayer.cpp @@ -563,6 +563,21 @@ bool AIPlayer::getAIMove(Move *movePtr) } } + Pose desiredPose = mPose; + + if ( mSwimming ) + desiredPose = SwimPose; + else if ( mAiPose == 1 && canCrouch() ) + desiredPose = CrouchPose; + else if ( mAiPose == 2 && canProne() ) + desiredPose = PronePose; + else if ( mAiPose == 3 && canSprint() ) + desiredPose = SprintPose; + else if ( canStand() ) + desiredPose = StandPose; + + setPose( desiredPose ); + // Replicate the trigger state into the move so that // triggers can be controlled from scripts. for( U32 i = 0; i < MaxTriggerKeys; i++ ) @@ -591,6 +606,16 @@ bool AIPlayer::getAIMove(Move *movePtr) return true; } +void AIPlayer::setAiPose( S32 pose ) +{ + mAiPose = pose; +} + +S32 AIPlayer::getAiPose() +{ + return mAiPose; +} + /** * Utility function to throw callbacks. Callbacks always occure * on the datablock class. @@ -1348,3 +1373,18 @@ DefineEngineMethod( AIPlayer, clearMoveTriggers, void, ( ),, { object->clearMoveTriggers(); } + +DefineEngineMethod( AIPlayer, setAiPose, void, ( S32 pose ),, + "@brief Sets the AiPose for an AI object.\n" + "@param pose StandPose=0, CrouchPose=1, PronePose=2, SprintPose=3.\n" + "Uses the new AiPose variable from shapebase (as defined in its PlayerData datablock).\n") +{ + object->setAiPose(pose); +} + +DefineEngineMethod( AIPlayer, getAiPose, S32, (),, + "@brief Get the object's current AiPose.\n" + "@return StandPose=0, CrouchPose=1, PronePose=2, SprintPose=3.\n") +{ + return object->getAiPose(); +} diff --git a/Engine/source/T3D/aiPlayer.h b/Engine/source/T3D/aiPlayer.h index d693314eb..f072d2423 100644 --- a/Engine/source/T3D/aiPlayer.h +++ b/Engine/source/T3D/aiPlayer.h @@ -179,7 +179,9 @@ public: void setMoveDestination( const Point3F &location, bool slowdown ); Point3F getMoveDestination() const { return mMoveDestination; } void stopMove(); - + void setAiPose( S32 pose ); + S32 getAiPose(); + // Trigger sets/gets void setMoveTrigger( U32 slot, const bool isSet = true ); bool getMoveTrigger( U32 slot ) const; diff --git a/Engine/source/T3D/player.cpp b/Engine/source/T3D/player.cpp index 6e33284a6..d05609523 100644 --- a/Engine/source/T3D/player.cpp +++ b/Engine/source/T3D/player.cpp @@ -3173,18 +3173,21 @@ void Player::updateMove(const Move* move) // Update the PlayerPose Pose desiredPose = mPose; - if ( mSwimming ) - desiredPose = SwimPose; - else if ( runSurface && move->trigger[sCrouchTrigger] && canCrouch() ) - desiredPose = CrouchPose; - else if ( runSurface && move->trigger[sProneTrigger] && canProne() ) - desiredPose = PronePose; - else if ( move->trigger[sSprintTrigger] && canSprint() ) - desiredPose = SprintPose; - else if ( canStand() ) - desiredPose = StandPose; + if ( !mIsAiControlled ) + { + if ( mSwimming ) + desiredPose = SwimPose; + else if ( runSurface && move->trigger[sCrouchTrigger] && canCrouch() ) + desiredPose = CrouchPose; + else if ( runSurface && move->trigger[sProneTrigger] && canProne() ) + desiredPose = PronePose; + else if ( move->trigger[sSprintTrigger] && canSprint() ) + desiredPose = SprintPose; + else if ( canStand() ) + desiredPose = StandPose; - setPose( desiredPose ); + setPose( desiredPose ); + } } @@ -6186,6 +6189,10 @@ U32 Player::packUpdate(NetConnection *con, U32 mask, BitStream *stream) { stream->writeFlag(mFalling); + stream->writeFlag(mSwimming); + stream->writeFlag(mJetting); + stream->writeInt(mPose, NumPoseBits); + stream->writeInt(mState,NumStateBits); if (stream->writeFlag(mState == RecoverState)) stream->writeInt(mRecoverTicks,PlayerData::RecoverDelayBits); @@ -6282,7 +6289,11 @@ void Player::unpackUpdate(NetConnection *con, BitStream *stream) if (stream->readFlag()) { mPredictionCount = sMaxPredictionTicks; mFalling = stream->readFlag(); - + + mSwimming = stream->readFlag(); + mJetting = stream->readFlag(); + mPose = (Pose)(stream->readInt(NumPoseBits)); + ActionState actionState = (ActionState)stream->readInt(NumStateBits); if (stream->readFlag()) { mRecoverTicks = stream->readInt(PlayerData::RecoverDelayBits); diff --git a/Engine/source/T3D/shapeBase.cpp b/Engine/source/T3D/shapeBase.cpp index 55b258a72..52ea070e3 100644 --- a/Engine/source/T3D/shapeBase.cpp +++ b/Engine/source/T3D/shapeBase.cpp @@ -879,6 +879,7 @@ IMPLEMENT_CALLBACK( ShapeBase, validateCameraFov, F32, (F32 fov), (fov), ShapeBase::ShapeBase() : mDataBlock( NULL ), mIsAiControlled( false ), + mAiPose( 0 ), mControllingObject( NULL ), mMoveMotion( false ), mShapeBaseMount( NULL ), diff --git a/Engine/source/T3D/shapeBase.h b/Engine/source/T3D/shapeBase.h index fcada642b..5a7ff5eb1 100644 --- a/Engine/source/T3D/shapeBase.h +++ b/Engine/source/T3D/shapeBase.h @@ -874,6 +874,7 @@ protected: /// @name Physical Properties /// @{ + S32 mAiPose; ///< Current pose. F32 mEnergy; ///< Current enery level. F32 mRechargeRate; ///< Energy recharge rate (in units/tick). From 0fb62de4b8faa711d8fe544516fed653cb409701 Mon Sep 17 00:00:00 2001 From: "Anis A. Hireche" Date: Fri, 26 Feb 2016 18:41:29 +0100 Subject: [PATCH 104/109] restored old signature of mRound as it's used from scripts. --- Engine/source/math/mConsoleFunctions.cpp | 15 +++++---------- 1 file changed, 5 insertions(+), 10 deletions(-) diff --git a/Engine/source/math/mConsoleFunctions.cpp b/Engine/source/math/mConsoleFunctions.cpp index 679dad3e2..1a11fe23e 100644 --- a/Engine/source/math/mConsoleFunctions.cpp +++ b/Engine/source/math/mConsoleFunctions.cpp @@ -94,18 +94,13 @@ DefineConsoleFunction( mFloor, S32, ( F32 v ),, return (S32)mFloor( v ); } - -DefineConsoleFunction( mRound, F32, ( F32 v, S32 n ), (0), +DefineConsoleFunction( mRound, S32, ( F32 v ),, "Round v to the nth decimal place or the nearest whole number by default." - "@param v Value to round\n" - "@param n Number of decimal places to round to, 0 by default\n" - "@return The rounded value as a S32." - "@ingroup Math" ) + "@param v Value to roundn" + "@return The rounded value as a S32." + "@ingroup Math" ) { - if(n <= 0) - return mRound(v); - else - return mRound(v, n); + return mRound(v); } DefineConsoleFunction( mCeil, S32, ( F32 v ),, From 973e5a6c0209502ec036dc6bcf7fb64f470dc3f6 Mon Sep 17 00:00:00 2001 From: Anis Date: Fri, 26 Feb 2016 20:11:27 +0100 Subject: [PATCH 105/109] Update consoleFunctions.cpp --- Engine/source/console/consoleFunctions.cpp | 472 +++++++++++++++++++++ 1 file changed, 472 insertions(+) diff --git a/Engine/source/console/consoleFunctions.cpp b/Engine/source/console/consoleFunctions.cpp index c03c68046..be265add8 100644 --- a/Engine/source/console/consoleFunctions.cpp +++ b/Engine/source/console/consoleFunctions.cpp @@ -25,6 +25,11 @@ #include "console/consoleInternal.h" #include "console/engineAPI.h" #include "console/ast.h" + +#ifndef _CONSOLFUNCTIONS_H_ +#include "console/consoleFunctions.h" +#endif + #include "core/strings/findMatch.h" #include "core/strings/stringUnit.h" #include "core/strings/unicode.h" @@ -32,6 +37,7 @@ #include "console/compiler.h" #include "platform/platformInput.h" #include "core/util/journal/journal.h" +#include "gfx/gfxEnums.h" #include "core/util/uuid.h" #include "core/color.h" #include "math/mPoint3.h" @@ -44,6 +50,132 @@ bool LinkConsoleFunctions = false; // Buffer for expanding script filenames. static char scriptFilenameBuffer[1024]; +bool isInt(const char* str) +{ + int len = dStrlen(str); + if(len <= 0) + return false; + + // Ignore whitespace + int start = 0; + for(int i = start; i < len; i++) + if(str[i] != ' ') + { + start = i; + break; + } + + for(int i = start; i < len; i++) + switch(str[i]) + { + case '+': case '-': + if(i != 0) + return false; + break; + case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9': case '0': + break; + case ' ': // ignore whitespace + for(int j = i+1; j < len; j++) + if(str[j] != ' ') + return false; + return true; + break; + default: + return false; + } + return true; +} + +bool isFloat(const char* str, bool sciOk = false) +{ + int len = dStrlen(str); + if(len <= 0) + return false; + + // Ingore whitespace + int start = 0; + for(int i = start; i < len; i++) + if(str[i] != ' ') + { + start = i; + break; + } + + bool seenDot = false; + int eLoc = -1; + for(int i = 0; i < len; i++) + switch(str[i]) + { + case '+': case '-': + if(sciOk) + { + //Haven't found e or scientific notation symbol + if(eLoc == -1) + { + //only allowed in beginning + if(i != 0) + return false; + } + else + { + //if not right after the e + if(i != (eLoc + 1)) + return false; + } + } + else + { + //only allowed in beginning + if(i != 0) + return false; + } + break; + case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9': case '0': + break; + case 'e': case 'E': + if(!sciOk) + return false; + else + { + //already saw it so can't have 2 + if(eLoc != -1) + return false; + + eLoc = i; + } + break; + case '.': + if(seenDot | (sciOk && eLoc != -1)) + return false; + seenDot = true; + break; + case ' ': // ignore whitespace + for(int j = i+1; j < len; j++) + if(str[j] != ' ') + return false; + return true; + break; + default: + return false; + } + return true; +} + +bool isValidIP(const char* ip) +{ + unsigned b1, b2, b3, b4; + unsigned char c; + int rc = dSscanf(ip, "%3u.%3u.%3u.%3u%c", &b1, &b2, &b3, &b4, &c); + if (rc != 4 && rc != 5) return false; + if ((b1 | b2 | b3 | b4) > 255) return false; + if (dStrspn(ip, "0123456789.") < dStrlen(ip)) return false; + return true; +} + +bool isValidPort(U16 port) +{ + return (port >= 0 && port <=65535); +} //============================================================================= // String Functions. @@ -238,6 +370,40 @@ DefineConsoleFunction( strlen, S32, ( const char* str ),, return dStrlen( str ); } +//----------------------------------------------------------------------------- +DefineConsoleFunction( strlenskip, S32, ( const char* str, const char* first, const char* last ),, + "Calculate the length of a string in characters, skipping everything between and including first and last.\n" + "@param str A string.\n" + "@param first First character to look for to skip block of text.\n" + "@param last Second character to look for to skip block of text.\n" + "@return The length of the given string skipping blocks of text between characters.\n" + "@ingroup Strings" ) +{ + const UTF8* pos = str; + U32 size = 0; + U32 length = dStrlen(str); + bool count = true; + + //loop through each character counting each character, skipping tags (anything with < followed by >) + for(U32 i = 0; i < length; i++, pos++) + { + if(count) + { + if(*pos == first[0]) + count = false; + else + size++; + } + else + { + if(*pos == last[0]) + count = true; + } + } + + return S32(size); +} + //----------------------------------------------------------------------------- DefineConsoleFunction( strstr, S32, ( const char* string, const char* substring ),, @@ -284,6 +450,33 @@ DefineConsoleFunction( strpos, S32, ( const char* haystack, const char* needle, //----------------------------------------------------------------------------- +DefineConsoleFunction( strposr, S32, ( const char* haystack, const char* needle, S32 offset ), ( 0 ), + "Find the start of @a needle in @a haystack searching from right to left beginning at the given offset.\n" + "@param haystack The string to search.\n" + "@param needle The string to search for.\n" + "@return The index at which the first occurrence of @a needle was found in @a heystack or -1 if no match was found.\n\n" + "@tsexample\n" + "strposr( \"b ab\", \"b\", 1 ) // Returns 2.\n" + "@endtsexample\n" + "@ingroup Strings" ) +{ + U32 sublen = dStrlen( needle ); + U32 strlen = dStrlen( haystack ); + S32 start = strlen - offset; + + if(start < 0 || start > strlen) + return -1; + + if (start + sublen > strlen) + start = strlen - sublen; + for(; start >= 0; start--) + if(!dStrncmp(haystack + start, needle, sublen)) + return start; + return -1; +} + +//----------------------------------------------------------------------------- + DefineConsoleFunction( ltrim, const char*, ( const char* str ),, "Remove leading whitespace from the string.\n" "@param str A string.\n" @@ -630,6 +823,18 @@ DefineConsoleFunction( stripTrailingNumber, String, ( const char* str ),, return String::GetTrailingNumber( str, suffix ); } +//----------------------------------------------------------------------------- + +DefineConsoleFunction( getFirstNumber, String, ( const char* str ),, + "Get the first occuring number from @a str.\n" + "@param str The string from which to read out the first number.\n" + "@return String representation of the number or "" if no number.\n\n") +{ + U32 start; + U32 end; + return String::GetFirstNumber(str, start, end); +} + //---------------------------------------------------------------- DefineConsoleFunction( isspace, bool, ( const char* str, S32 index ),, @@ -896,6 +1101,110 @@ DefineConsoleFunction(ColorHSBToRGB, ColorI, (Point3I hsb), , return color; } +//---------------------------------------------------------------- + +DefineConsoleFunction( strToggleCaseToWords, const char*, ( const char* str ),, + "Parse a Toggle Case word into separate words.\n" + "@param str The string to parse.\n" + "@return new string space separated.\n\n" + "@tsexample\n" + "strToggleCaseToWords( \"HelloWorld\" ) // Returns \"Hello World\".\n" + "@endtsexample\n" + "@ingroup Strings" ) +{ + String newStr; + for(S32 i = 0; str[i]; i++) + { + //If capitol add a space + if(i != 0 && str[i] >= 65 && str[i] <= 90) + newStr += " "; + + newStr += str[i]; + } + + return Con::getReturnBuffer(newStr); +} + +//---------------------------------------------------------------- + +// Warning: isInt and isFloat are very 'strict' and might need to be adjusted to allow other values. //seanmc +DefineConsoleFunction( isInt, bool, ( const char* str),, + "Returns true if the string is an integer.\n" + "@param str The string to test.\n" + "@return true if @a str is an integer and false if not\n\n" + "@tsexample\n" + "isInt( \"13\" ) // Returns true.\n" + "@endtsexample\n" + "@ingroup Strings" ) +{ + return isInt(str); +} + +//---------------------------------------------------------------- + +DefineConsoleFunction( isFloat, bool, ( const char* str, bool sciOk), (false), + "Returns true if the string is a float.\n" + "@param str The string to test.\n" + "@param sciOk Test for correct scientific notation and accept it (ex. 1.2e+14)" + "@return true if @a str is a float and false if not\n\n" + "@tsexample\n" + "isFloat( \"13.5\" ) // Returns true.\n" + "@endtsexample\n" + "@ingroup Strings" ) +{ + return isFloat(str, sciOk); +} + +//---------------------------------------------------------------- + +DefineConsoleFunction( isValidPort, bool, ( const char* str),, + "Returns true if the string is a valid port number.\n" + "@param str The string to test.\n" + "@return true if @a str is a port and false if not\n\n" + "@tsexample\n" + "isValidPort( \"8080\" ) // Returns true.\n" + "@endtsexample\n" + "@ingroup Strings" ) +{ + if(isInt(str)) + { + U16 port = dAtous(str); + return isValidPort(port); + } + else + return false; +} + +//---------------------------------------------------------------- + +DefineConsoleFunction( isValidIP, bool, ( const char* str),, + "Returns true if the string is a valid ip address, excepts localhost.\n" + "@param str The string to test.\n" + "@return true if @a str is a valid ip address and false if not\n\n" + "@tsexample\n" + "isValidIP( \"localhost\" ) // Returns true.\n" + "@endtsexample\n" + "@ingroup Strings" ) +{ + if(dStrcmp(str, "localhost") == 0) + { + return true; + } + else + return isValidIP(str); +} + +//---------------------------------------------------------------- + +// Torque won't normally add another string if it already exists with another casing, +// so this forces the addition. It should be called once near the start, such as in main.cs. +ConsoleFunction(addCaseSensitiveStrings,void,2,0,"[string1, string2, ...]" + "Adds case sensitive strings to the StringTable.") +{ + for(int i = 1; i < argc; i++) + StringTable->insert(argv[i], true); +} + //============================================================================= // Field Manipulators. //============================================================================= @@ -914,6 +1223,7 @@ DefineConsoleFunction( getWord, const char*, ( const char* text, S32 index ),, "@endtsexample\n\n" "@see getWords\n" "@see getWordCount\n" + "@see getToken\n" "@see getField\n" "@see getRecord\n" "@ingroup FieldManip" ) @@ -937,6 +1247,7 @@ DefineConsoleFunction( getWords, const char*, ( const char* text, S32 startIndex "@endtsexample\n\n" "@see getWord\n" "@see getWordCount\n" + "@see getTokens\n" "@see getFields\n" "@see getRecords\n" "@ingroup FieldManip" ) @@ -961,6 +1272,7 @@ DefineConsoleFunction( setWord, const char*, ( const char* text, S32 index, cons "setWord( \"a b c d\", 2, \"f\" ) // Returns \"a b f d\"\n" "@endtsexample\n\n" "@see getWord\n" + "@see setToken\n" "@see setField\n" "@see setRecord\n" "@ingroup FieldManip" ) @@ -980,6 +1292,7 @@ DefineConsoleFunction( removeWord, const char*, ( const char* text, S32 index ), "@tsexample\n" "removeWord( \"a b c d\", 2 ) // Returns \"a b d\"\n" "@endtsexample\n\n" + "@see removeToken\n" "@see removeField\n" "@see removeRecord\n" "@ingroup FieldManip" ) @@ -997,6 +1310,7 @@ DefineConsoleFunction( getWordCount, S32, ( const char* text ),, "@tsexample\n" "getWordCount( \"a b c d e\" ) // Returns 5\n" "@endtsexample\n\n" + "@see getTokenCount\n" "@see getFieldCount\n" "@see getRecordCount\n" "@ingroup FieldManip" ) @@ -1006,6 +1320,49 @@ DefineConsoleFunction( getWordCount, S32, ( const char* text ),, //----------------------------------------------------------------------------- +DefineEngineFunction( monthNumToStr, String, ( S32 num, bool abbreviate ), (false), + "@brief returns month as a word given a number or \"\" if number is bad" + "@return month as a word given a number or \"\" if number is bad" + "@ingroup FileSystem") +{ + switch(num) + { + case 1: return abbreviate ? "Jan" : "January"; break; + case 2: return abbreviate ? "Feb" : "February"; break; + case 3: return abbreviate ? "Mar" : "March"; break; + case 4: return abbreviate ? "Apr" : "April"; break; + case 5: return "May"; break; + case 6: return abbreviate ? "Jun" : "June"; break; + case 7: return abbreviate ? "Jul" : "July"; break; + case 8: return abbreviate ? "Aug" : "August"; break; + case 9: return abbreviate ? "Sep" : "September"; break; + case 10: return abbreviate ? "Oct" : "October"; break; + case 11: return abbreviate ? "Nov" : "November"; break; + case 12: return abbreviate ? "Dec" : "December"; break; + default: return ""; + } +} + +DefineEngineFunction( weekdayNumToStr, String, ( S32 num, bool abbreviate ), (false), + "@brief returns weekday as a word given a number or \"\" if number is bad" + "@return weekday as a word given a number or \"\" if number is bad" + "@ingroup FileSystem") +{ + switch(num) + { + case 0: return abbreviate ? "Sun" : "Sunday"; break; + case 1: return abbreviate ? "Mon" : "Monday"; break; + case 2: return abbreviate ? "Tue" : "Tuesday"; break; + case 3: return abbreviate ? "Wed" : "Wednesday"; break; + case 4: return abbreviate ? "Thu" : "Thursday"; break; + case 5: return abbreviate ? "Fri" : "Friday"; break; + case 6: return abbreviate ? "Sat" : "Saturday"; break; + default: return ""; + } +} + +//----------------------------------------------------------------------------- + DefineConsoleFunction( getField, const char*, ( const char* text, S32 index ),, "Extract the field at the given @a index in the newline and/or tab separated list in @a text.\n" "Fields in @a text must be separated by newlines and/or tabs.\n" @@ -1327,6 +1684,114 @@ DefineConsoleFunction( nextToken, const char*, ( const char* str1, const char* t return ret; } +//----------------------------------------------------------------------------- + +DefineConsoleFunction( getToken, const char*, ( const char* text, const char* delimiters, S32 index ),, + "Extract the substring at the given @a index in the @a delimiters separated list in @a text.\n" + "@param text A @a delimiters list of substrings.\n" + "@param delimiters Character or characters that separate the list of substrings in @a text.\n" + "@param index The zero-based index of the substring to extract.\n" + "@return The substring at the given index or \"\" if the index is out of range.\n\n" + "@tsexample\n" + "getToken( \"a b c d\", \" \", 2 ) // Returns \"c\"\n" + "@endtsexample\n\n" + "@see getTokens\n" + "@see getTokenCount\n" + "@see getWord\n" + "@see getField\n" + "@see getRecord\n" + "@ingroup FieldManip" ) +{ + return Con::getReturnBuffer( StringUnit::getUnit(text, index, delimiters)); +} + +//----------------------------------------------------------------------------- + +DefineConsoleFunction( getTokens, const char*, ( const char* text, const char* delimiters, S32 startIndex, S32 endIndex ), ( -1 ), + "Extract a range of substrings separated by @a delimiters at the given @a startIndex onwards thru @a endIndex.\n" + "@param text A @a delimiters list of substrings.\n" + "@param delimiters Character or characters that separate the list of substrings in @a text.\n" + "@param startIndex The zero-based index of the first substring to extract from @a text.\n" + "@param endIndex The zero-based index of the last substring to extract from @a text. If this is -1, all words beginning " + "with @a startIndex are extracted from @a text.\n" + "@return A string containing the specified range of substrings from @a text or \"\" if @a startIndex " + "is out of range or greater than @a endIndex.\n\n" + "@tsexample\n" + "getTokens( \"a b c d\", \" \", 1, 2, ) // Returns \"b c\"\n" + "@endtsexample\n\n" + "@see getToken\n" + "@see getTokenCount\n" + "@see getWords\n" + "@see getFields\n" + "@see getRecords\n" + "@ingroup FieldManip" ) +{ + if( endIndex < 0 ) + endIndex = 1000000; + + return Con::getReturnBuffer( StringUnit::getUnits( text, startIndex, endIndex, delimiters ) ); +} + +//----------------------------------------------------------------------------- + +DefineConsoleFunction( setToken, const char*, ( const char* text, const char* delimiters, S32 index, const char* replacement ),, + "Replace the substring in @a text separated by @a delimiters at the given @a index with @a replacement.\n" + "@param text A @a delimiters list of substrings.\n" + "@param delimiters Character or characters that separate the list of substrings in @a text.\n" + "@param index The zero-based index of the substring to replace.\n" + "@param replacement The string with which to replace the substring.\n" + "@return A new string with the substring at the given @a index replaced by @a replacement or the original " + "string if @a index is out of range.\n\n" + "@tsexample\n" + "setToken( \"a b c d\", \" \", 2, \"f\" ) // Returns \"a b f d\"\n" + "@endtsexample\n\n" + "@see getToken\n" + "@see setWord\n" + "@see setField\n" + "@see setRecord\n" + "@ingroup FieldManip" ) +{ + return Con::getReturnBuffer( StringUnit::setUnit( text, index, replacement, delimiters) ); +} + +//----------------------------------------------------------------------------- + +DefineConsoleFunction( removeToken, const char*, ( const char* text, const char* delimiters, S32 index ),, + "Remove the substring in @a text separated by @a delimiters at the given @a index.\n" + "@param text A @a delimiters list of substrings.\n" + "@param delimiters Character or characters that separate the list of substrings in @a text.\n" + "@param index The zero-based index of the word in @a text.\n" + "@return A new string with the substring at the given index removed or the original string if @a index is " + "out of range.\n\n" + "@tsexample\n" + "removeToken( \"a b c d\", \" \", 2 ) // Returns \"a b d\"\n" + "@endtsexample\n\n" + "@see removeWord\n" + "@see removeField\n" + "@see removeRecord\n" + "@ingroup FieldManip" ) +{ + return Con::getReturnBuffer( StringUnit::removeUnit( text, index, delimiters ) ); +} + +//----------------------------------------------------------------------------- + +DefineConsoleFunction( getTokenCount, S32, ( const char* text, const char* delimiters),, + "Return the number of @a delimiters substrings in @a text.\n" + "@param text A @a delimiters list of substrings.\n" + "@param delimiters Character or characters that separate the list of substrings in @a text.\n" + "@return The number of @a delimiters substrings in @a text.\n\n" + "@tsexample\n" + "getTokenCount( \"a b c d e\", \" \" ) // Returns 5\n" + "@endtsexample\n\n" + "@see getWordCount\n" + "@see getFieldCount\n" + "@see getRecordCount\n" + "@ingroup FieldManip" ) +{ + return StringUnit::getUnitCount( text, delimiters ); +} + //============================================================================= // Tagged Strings. //============================================================================= @@ -2682,3 +3147,10 @@ DefineEngineFunction( isToolBuild, bool, (),, return false; #endif } + +DefineEngineFunction( getMaxDynamicVerts, S32, (),, + "Get max number of allowable dynamic vertices in a single vertex buffer.\n\n" + "@return the max number of allowable dynamic vertices in a single vertex buffer" ) +{ + return MAX_DYNAMIC_VERTS / 2; +} From 49443d3167f581d60ff1a03d3314d7ac1f7c013f Mon Sep 17 00:00:00 2001 From: "Anis A. Hireche" Date: Sat, 27 Feb 2016 02:16:07 +0100 Subject: [PATCH 106/109] conflict resolution --- Engine/source/console/consoleObject.h | 268 ++++++++++++++----------- Engine/source/console/engineTypeInfo.h | 2 +- 2 files changed, 154 insertions(+), 116 deletions(-) diff --git a/Engine/source/console/consoleObject.h b/Engine/source/console/consoleObject.h index 5f04435aa..8ecbb8031 100644 --- a/Engine/source/console/consoleObject.h +++ b/Engine/source/console/consoleObject.h @@ -567,122 +567,141 @@ extern AbstractClassRep::FieldList sg_tempFieldList; /// @see AbtractClassRep /// @see ConsoleObject template< class T > -class ConcreteClassRep : public AbstractClassRep +class ConcreteAbstractClassRep : public AbstractClassRep { - public: +public: - static EnginePropertyTable _smPropertyTable; - static EnginePropertyTable& smPropertyTable; + virtual AbstractClassRep* getContainerChildClass(const bool recurse) + { + // Fetch container children type. + AbstractClassRep* pChildren = T::getContainerChildStaticClassRep(); + if (!recurse || pChildren != NULL) + return pChildren; - ConcreteClassRep( const char* name, - const char* conTypeName, - S32* conTypeIdPtr, - S32 netClassGroupMask, - S32 netClassType, - S32 netEventDir, - AbstractClassRep* parent, - const char* ( *parentDesc )() ) - : AbstractClassRep( conTypeIdPtr, conTypeName ) + // Fetch parent type. + AbstractClassRep* pParent = T::getParentStaticClassRep(); + if (pParent == NULL) + return NULL; + + // Get parent container children. + return pParent->getContainerChildClass(recurse); + } + + virtual WriteCustomTamlSchema getCustomTamlSchema(void) + { + return T::getStaticWriteCustomTamlSchema(); + } + + static EnginePropertyTable _smPropertyTable; + static EnginePropertyTable& smPropertyTable; + + ConcreteAbstractClassRep(const char* name, + const char* conTypeName, + S32* conTypeIdPtr, + S32 netClassGroupMask, + S32 netClassType, + S32 netEventDir, + AbstractClassRep* parent, + const char* (*parentDesc)()) + : AbstractClassRep(conTypeIdPtr, conTypeName) + { + mClassName = StringTable->insert(name); + mCategory = T::__category(); + mTypeInfo = _MAPTYPE< T >(); + + if (mTypeInfo) + const_cast< EngineTypeInfo* >(mTypeInfo)->mPropertyTable = &smPropertyTable; + + if (&T::__description != parentDesc) + mDescription = T::__description(); + + // Clean up mClassId + for (U32 i = 0; i < NetClassGroupsCount; i++) + mClassId[i] = -1; + + // Set properties for this ACR + mClassType = netClassType; + mClassGroupMask = netClassGroupMask; + mNetEventDir = netEventDir; + parentClass = parent; + mClassSizeof = sizeof(T); + + // Finally, register ourselves. + registerClassRep(this); + }; + + /// Wrap constructor. + ConsoleObject* create() const { return NULL; } + + /// Perform class specific initialization tasks. + /// + /// Link namespaces, call initPersistFields() and consoleInit(). + void init() + { + // Get handle to our parent class, if any, and ourselves (we are our parent's child). + AbstractClassRep *parent = T::getParentStaticClassRep(); + AbstractClassRep *child = T::getStaticClassRep(); + + // If we got reps, then link those namespaces! (To get proper inheritance.) + if (parent && child) + Con::classLinkNamespaces(parent->getNameSpace(), child->getNameSpace()); + + // Finally, do any class specific initialization... + T::initPersistFields(); + T::consoleInit(); + + // Let the base finish up. + AbstractClassRep::init(); + } + + /// @name Console Type Interface + /// @{ + + virtual void setData(void* dptr, S32 argc, const char** argv, const EnumTable* tbl, BitSet32 flag) + { + if (argc == 1) { - mClassName = StringTable->insert( name ); - mCategory = T::__category(); - mTypeInfo = _MAPTYPE< T >(); - - if( mTypeInfo ) - const_cast< EngineTypeInfo* >( mTypeInfo )->mPropertyTable = &smPropertyTable; - - if( &T::__description != parentDesc ) - mDescription = T::__description(); - - // Clean up mClassId - for(U32 i = 0; i < NetClassGroupsCount; i++) - mClassId[i] = -1; - - // Set properties for this ACR - mClassType = netClassType; - mClassGroupMask = netClassGroupMask; - mNetEventDir = netEventDir; - parentClass = parent; - mClassSizeof = sizeof(T); - - // Finally, register ourselves. - registerClassRep(this); - }; - - virtual AbstractClassRep* getContainerChildClass(const bool recurse) - { - // Fetch container children type. - AbstractClassRep* pChildren = T::getContainerChildStaticClassRep(); - if (!recurse || pChildren != NULL) - return pChildren; - - // Fetch parent type. - AbstractClassRep* pParent = T::getParentStaticClassRep(); - if (pParent == NULL) - return NULL; - - // Get parent container children. - return pParent->getContainerChildClass(recurse); + T** obj = (T**)dptr; + *obj = dynamic_cast< T* >(T::__findObject(argv[0])); } + else + Con::errorf("Cannot set multiple args to a single ConsoleObject*."); + } + + virtual const char* getData(void* dptr, const EnumTable* tbl, BitSet32 flag) + { + T** obj = (T**)dptr; + return Con::getReturnBuffer(T::__getObjectId(*obj)); + } + + virtual const char* getTypeClassName() { return mClassName; } + virtual const bool isDatablock() { return T::__smIsDatablock; }; - virtual WriteCustomTamlSchema getCustomTamlSchema(void) - { - return T::getStaticWriteCustomTamlSchema(); - } - - /// Perform class specific initialization tasks. - /// - /// Link namespaces, call initPersistFields() and consoleInit(). - void init() - { - // Get handle to our parent class, if any, and ourselves (we are our parent's child). - AbstractClassRep *parent = T::getParentStaticClassRep(); - AbstractClassRep *child = T::getStaticClassRep(); - - // If we got reps, then link those namespaces! (To get proper inheritance.) - if(parent && child) - Con::classLinkNamespaces(parent->getNameSpace(), child->getNameSpace()); - - // Finally, do any class specific initialization... - T::initPersistFields(); - T::consoleInit(); - - // Let the base finish up. - AbstractClassRep::init(); - } - - /// Wrap constructor. - ConsoleObject* create() const { return new T; } - - /// @name Console Type Interface - /// @{ - - virtual void setData( void* dptr, S32 argc, const char** argv, const EnumTable* tbl, BitSet32 flag ) - { - if( argc == 1 ) - { - T** obj = ( T** ) dptr; - *obj = dynamic_cast< T* >( T::__findObject( argv[ 0 ] ) ); - } - else - Con::errorf( "Cannot set multiple args to a single ConsoleObject*."); - } - - virtual const char* getData( void* dptr, const EnumTable* tbl, BitSet32 flag ) - { - T** obj = ( T** ) dptr; - return Con::getReturnBuffer( T::__getObjectId( *obj ) ); - } - - virtual const char* getTypeClassName() { return mClassName; } - virtual const bool isDatablock() { return T::__smIsDatablock; }; - - /// @} + /// @} + }; + + template< class T > + class ConcreteClassRep : public ConcreteAbstractClassRep + { + public: + ConcreteClassRep(const char* name, + const char* conTypeName, + S32* conTypeIdPtr, + S32 netClassGroupMask, + S32 netClassType, + S32 netEventDir, + AbstractClassRep* parent, + const char* (*parentDesc)()) + : ConcreteAbstractClassRep(name, conTypeName, conTypeIdPtr, netClassGroupMask, netClassType, netEventDir, parent, parentDesc) + { + } + + /// Wrap constructor. + ConsoleObject* create() const { return new T; } }; -template< typename T > EnginePropertyTable ConcreteClassRep< T >::_smPropertyTable( 0, NULL ); -template< typename T > EnginePropertyTable& ConcreteClassRep< T >::smPropertyTable = ConcreteClassRep< T >::_smPropertyTable; - +template< typename T > EnginePropertyTable ConcreteAbstractClassRep< T >::_smPropertyTable(0, NULL); +template< typename T > EnginePropertyTable& ConcreteAbstractClassRep< T >::smPropertyTable = ConcreteAbstractClassRep< T >::_smPropertyTable; //------------------------------------------------------------------------------ // Forward declaration of this function so it can be used in the class @@ -763,6 +782,15 @@ public: /// Gets the ClassRep. virtual AbstractClassRep* getClassRep() const; +#define DECLARE_ABSTRACT_CONOBJECT( className ) \ + DECLARE_ABSTRACT_CLASS( className, Parent ); \ + static S32 _smTypeId; \ + static ConcreteAbstractClassRep< className > dynClassRep; \ + static AbstractClassRep* getParentStaticClassRep(); \ + static AbstractClassRep* getStaticClassRep(); \ + static SimObjectRefConsoleBaseType< className > ptrRefType; \ + virtual AbstractClassRep* getClassRep() const + /// Set the value of a field. bool setField(const char *fieldName, const char *value); @@ -1136,7 +1164,7 @@ inline bool& ConsoleObject::getDynamicGroupExpand() AbstractClassRep::WriteCustomTamlSchema className::getStaticWriteCustomTamlSchema() { return NULL; } \ ConcreteClassRep className::dynClassRep( #className, "Type" #className, &_smTypeId, 0, -1, 0, className::getParentStaticClassRep(), &Parent::__description ) -#define IMPLEMENT_CONOBJECT_CHILDREN( className ) \ +#define IMPLEMENT_CONOBJECT_CHILDREN( className ) \ IMPLEMENT_CLASS( className, NULL ) \ END_IMPLEMENT_CLASS; \ S32 className::_smTypeId; \ @@ -1144,11 +1172,11 @@ inline bool& ConsoleObject::getDynamicGroupExpand() AbstractClassRep* className::getClassRep() const { return &className::dynClassRep; } \ AbstractClassRep* className::getStaticClassRep() { return &dynClassRep; } \ AbstractClassRep* className::getParentStaticClassRep() { return Parent::getStaticClassRep(); } \ - AbstractClassRep* className::getContainerChildStaticClassRep() { return Children::getStaticClassRep(); } \ + AbstractClassRep* className::getContainerChildStaticClassRep() { return Children::getStaticClassRep(); } \ AbstractClassRep::WriteCustomTamlSchema className::getStaticWriteCustomTamlSchema() { return NULL; } \ ConcreteClassRep className::dynClassRep( #className, "Type" #className, &_smTypeId, 0, -1, 0, className::getParentStaticClassRep(), &Parent::__description ) -#define IMPLEMENT_CONOBJECT_SCHEMA( className, schema ) \ +#define IMPLEMENT_CONOBJECT_SCHEMA( className, schema ) \ IMPLEMENT_CLASS( className, NULL ) \ END_IMPLEMENT_CLASS; \ S32 className::_smTypeId; \ @@ -1160,7 +1188,7 @@ inline bool& ConsoleObject::getDynamicGroupExpand() AbstractClassRep::WriteCustomTamlSchema className::getStaticWriteCustomTamlSchema() { return schema; } \ ConcreteClassRep className::dynClassRep( #className, "Type" #className, &_smTypeId, 0, -1, 0, className::getParentStaticClassRep(), &Parent::__description ) -#define IMPLEMENT_CONOBJECT_CHILDREN_SCHEMA( className, schema ) \ +#define IMPLEMENT_CONOBJECT_CHILDREN_SCHEMA( className, schema ) \ IMPLEMENT_CLASS( className, NULL ) \ END_IMPLEMENT_CLASS; \ S32 className::_smTypeId; \ @@ -1168,10 +1196,20 @@ inline bool& ConsoleObject::getDynamicGroupExpand() AbstractClassRep* className::getClassRep() const { return &className::dynClassRep; } \ AbstractClassRep* className::getStaticClassRep() { return &dynClassRep; } \ AbstractClassRep* className::getParentStaticClassRep() { return Parent::getStaticClassRep(); } \ - AbstractClassRep* className::getContainerChildStaticClassRep() { return Children::getStaticClassRep(); } \ + AbstractClassRep* className::getContainerChildStaticClassRep() { return Children::getStaticClassRep(); } \ AbstractClassRep::WriteCustomTamlSchema className::getStaticWriteCustomTamlSchema() { return schema; } \ ConcreteClassRep className::dynClassRep( #className, "Type" #className, &_smTypeId, 0, -1, 0, className::getParentStaticClassRep(), &Parent::__description ) +#define IMPLEMENT_ABSTRACT_CONOBJECT( className ) \ + IMPLEMENT_NONINSTANTIABLE_CLASS( className, NULL ) \ + END_IMPLEMENT_CLASS; \ + S32 className::_smTypeId; \ + SimObjectRefConsoleBaseType< className > className::ptrRefType( "Type" #className "Ref" ); \ + AbstractClassRep* className::getClassRep() const { return &className::dynClassRep; } \ + AbstractClassRep* className::getStaticClassRep() { return &dynClassRep; } \ + AbstractClassRep* className::getParentStaticClassRep() { return Parent::getStaticClassRep(); } \ + ConcreteAbstractClassRep className::dynClassRep( #className, "Type" #className, &_smTypeId, 0, -1, 0, className::getParentStaticClassRep(), &Parent::__description ) + #define IMPLEMENT_CO_NETOBJECT_V1( className ) \ IMPLEMENT_CLASS( className, NULL ) \ END_IMPLEMENT_CLASS; \ @@ -1181,7 +1219,7 @@ inline bool& ConsoleObject::getDynamicGroupExpand() AbstractClassRep* className::getStaticClassRep() { return &dynClassRep; } \ AbstractClassRep* className::getParentStaticClassRep() { return Parent::getStaticClassRep(); } \ AbstractClassRep* className::getContainerChildStaticClassRep() { return NULL; } \ - AbstractClassRep::WriteCustomTamlSchema className::getStaticWriteCustomTamlSchema() { return NULL; } \ + AbstractClassRep::WriteCustomTamlSchema className::getStaticWriteCustomTamlSchema() { return NULL; } \ ConcreteClassRep className::dynClassRep( #className, "Type" #className, &_smTypeId, NetClassGroupGameMask, NetClassTypeObject, 0, className::getParentStaticClassRep(), &Parent::__description ) #define IMPLEMENT_CO_DATABLOCK_V1( className ) \ @@ -1193,7 +1231,7 @@ inline bool& ConsoleObject::getDynamicGroupExpand() AbstractClassRep* className::getStaticClassRep() { return &dynClassRep; } \ AbstractClassRep* className::getParentStaticClassRep() { return Parent::getStaticClassRep(); } \ AbstractClassRep* className::getContainerChildStaticClassRep() { return NULL; } \ - AbstractClassRep::WriteCustomTamlSchema className::getStaticWriteCustomTamlSchema() { return NULL; } \ + AbstractClassRep::WriteCustomTamlSchema className::getStaticWriteCustomTamlSchema() { return NULL; } \ ConcreteClassRep className::dynClassRep(#className, "Type" #className, &_smTypeId, NetClassGroupGameMask, NetClassTypeDataBlock, 0, className::getParentStaticClassRep(), &Parent::__description ) // Support for adding properties to classes CONOBJECT style. diff --git a/Engine/source/console/engineTypeInfo.h b/Engine/source/console/engineTypeInfo.h index 88d78eed7..52d713e9e 100644 --- a/Engine/source/console/engineTypeInfo.h +++ b/Engine/source/console/engineTypeInfo.h @@ -362,7 +362,7 @@ class EngineTypeInfo : public EngineExportScope // them to retroactively install property tables. Will be removed // when the console interop is removed and all classes are migrated // to the new system. - template< typename T > friend class ConcreteClassRep; + template< typename T > friend class ConcreteAbstractClassRep; protected: From c0e29d4a223cdb8ae88faa8a50d79771545672d3 Mon Sep 17 00:00:00 2001 From: Areloch Date: Sat, 27 Feb 2016 13:13:12 -0600 Subject: [PATCH 107/109] Correction for 2 of the render bin settings, as well as correcting the water shader over-exposing it's reflections. --- Templates/Full/game/core/scripts/client/renderManager.cs | 4 ++-- Templates/Full/game/shaders/common/water/gl/waterP.glsl | 2 +- Templates/Full/game/shaders/common/water/waterP.hlsl | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/Templates/Full/game/core/scripts/client/renderManager.cs b/Templates/Full/game/core/scripts/client/renderManager.cs index 71200ad49..a14287db1 100644 --- a/Templates/Full/game/core/scripts/client/renderManager.cs +++ b/Templates/Full/game/core/scripts/client/renderManager.cs @@ -55,8 +55,8 @@ function initRenderManager() DiffuseRenderPassManager.addManager( new RenderObjectMgr(BeginBin) { bintype = "Begin"; renderOrder = 0.2; processAddOrder = 0.2; } ); // Normal mesh rendering. - DiffuseRenderPassManager.addManager( new RenderTerrainMgr(TerrainBin) { renderOrder = 0.4; processAddOrder = 0.4; } ); - DiffuseRenderPassManager.addManager( new RenderMeshMgr(MeshBin) { bintype = "Mesh"; renderOrder = 0.5; processAddOrder = 0.5; } ); + DiffuseRenderPassManager.addManager( new RenderTerrainMgr(TerrainBin) { renderOrder = 0.4; processAddOrder = 0.4; basicOnly = true; } ); + DiffuseRenderPassManager.addManager( new RenderMeshMgr(MeshBin) { bintype = "Mesh"; renderOrder = 0.5; processAddOrder = 0.5; basicOnly = true; } ); DiffuseRenderPassManager.addManager( new RenderImposterMgr(ImposterBin) { renderOrder = 0.56; processAddOrder = 0.56; } ); DiffuseRenderPassManager.addManager( new RenderObjectMgr(ObjectBin) { bintype = "Object"; renderOrder = 0.6; processAddOrder = 0.6; } ); diff --git a/Templates/Full/game/shaders/common/water/gl/waterP.glsl b/Templates/Full/game/shaders/common/water/gl/waterP.glsl index 15002849f..a68ede84e 100644 --- a/Templates/Full/game/shaders/common/water/gl/waterP.glsl +++ b/Templates/Full/game/shaders/common/water/gl/waterP.glsl @@ -295,7 +295,7 @@ void main() foamColor.rgb *= FOAM_OPACITY * foamAmt * foamColor.a; // Get reflection map color. - vec4 refMapColor = hdrDecode( texture( reflectMap, reflectCoord ) ); + vec4 refMapColor = texture( reflectMap, reflectCoord ); // If we do not have a reflection texture then we use the cubemap. refMapColor = mix( refMapColor, texture( skyMap, reflectionVec ), NO_REFLECT ); diff --git a/Templates/Full/game/shaders/common/water/waterP.hlsl b/Templates/Full/game/shaders/common/water/waterP.hlsl index 2b4035755..c4edff0d7 100644 --- a/Templates/Full/game/shaders/common/water/waterP.hlsl +++ b/Templates/Full/game/shaders/common/water/waterP.hlsl @@ -282,7 +282,7 @@ float4 main( ConnectData IN ) : COLOR foamColor.rgb *= FOAM_OPACITY * foamAmt * foamColor.a; // Get reflection map color. - float4 refMapColor = hdrDecode( tex2D( reflectMap, reflectCoord ) ); + float4 refMapColor = tex2D( reflectMap, reflectCoord ); // If we do not have a reflection texture then we use the cubemap. refMapColor = lerp( refMapColor, texCUBE( skyMap, reflectionVec ), NO_REFLECT ); From 1c854b6009b6e1fed7a27d3e55dd2ddb41994d46 Mon Sep 17 00:00:00 2001 From: Azaezel Date: Mon, 29 Feb 2016 04:17:42 -0600 Subject: [PATCH 108/109] opengl crashfix pow(x,y) needed to be passed matching vartypes. --- Templates/Full/game/shaders/common/gl/torque.glsl | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Templates/Full/game/shaders/common/gl/torque.glsl b/Templates/Full/game/shaders/common/gl/torque.glsl index 65580cb7b..d4a7c4538 100644 --- a/Templates/Full/game/shaders/common/gl/torque.glsl +++ b/Templates/Full/game/shaders/common/gl/torque.glsl @@ -285,11 +285,11 @@ void fizzle(vec2 vpos, float visibility) #define assert(condition, color) { if(!any(condition)) { OUT_col = color; return; } } // Deferred Shading: Material Info Flag Check -bool getFlag(float flags, int num) +bool getFlag(float flags, float num) { float process = round(flags * 255); - float squareNum = pow(2, num); - return (mod(process, pow(2, squareNum)) >= squareNum); + float squareNum = pow(2.0, num); + return (mod(process, pow(2.0, squareNum)) >= squareNum); } // #define TORQUE_STOCK_GAMMA From 9472bfd3ca4fed4282bca7625401713a891881f6 Mon Sep 17 00:00:00 2001 From: Azaezel Date: Tue, 1 Mar 2016 20:25:52 -0600 Subject: [PATCH 109/109] addresses https://github.com/GarageGames/Torque3D/issues/1537 via the following: https://github.com/GarageGames/Torque3D/blob/908be4818f7e5de23f2070d18ed4df0f4dfc5ddf/Engine/source/materials/materialDefinition.cpp#L261 denotes power as sharpness, reflected in the size/falloff of a highlight halo * https://github.com/GarageGames/Torque3D/blob/908be4818f7e5de23f2070d18ed4df0f4dfc5ddf/Engine/source/materials/materialDefinition.cpp#L264 denotes strength as overall brightness of highlights. ** *and sharpness of reflection if using a cubemap. ** reflected in cubemapped objects as also degree of 'reflection' vs diffuse/albedo coloration. --- .../lighting/advanced/glsl/deferredShadingFeaturesGLSL.cpp | 4 ++-- .../lighting/advanced/hlsl/deferredShadingFeaturesHLSL.cpp | 4 ++-- Engine/source/shaderGen/GLSL/shaderFeatureGLSL.cpp | 4 ++-- Engine/source/shaderGen/HLSL/shaderFeatureHLSL.cpp | 4 ++-- 4 files changed, 8 insertions(+), 8 deletions(-) diff --git a/Engine/source/lighting/advanced/glsl/deferredShadingFeaturesGLSL.cpp b/Engine/source/lighting/advanced/glsl/deferredShadingFeaturesGLSL.cpp index e457b9eab..1dc906989 100644 --- a/Engine/source/lighting/advanced/glsl/deferredShadingFeaturesGLSL.cpp +++ b/Engine/source/lighting/advanced/glsl/deferredShadingFeaturesGLSL.cpp @@ -163,8 +163,8 @@ void DeferredSpecVarsGLSL::processPix( Vector &componentList, MultiLine *meta = new MultiLine; //matinfo.g slot reserved for AO later meta->addStatement(new GenOp(" @.g = 1.0;\r\n", material)); - meta->addStatement(new GenOp(" @.b = @/128;\r\n", material, specStrength)); - meta->addStatement(new GenOp(" @.a = @/5;\r\n", material, specPower)); + meta->addStatement(new GenOp(" @.a = @/128;\r\n", material, specPower)); + meta->addStatement(new GenOp(" @.b = @/5;\r\n", material, specStrength)); output = meta; } diff --git a/Engine/source/lighting/advanced/hlsl/deferredShadingFeaturesHLSL.cpp b/Engine/source/lighting/advanced/hlsl/deferredShadingFeaturesHLSL.cpp index 6b8b990a0..3e42af953 100644 --- a/Engine/source/lighting/advanced/hlsl/deferredShadingFeaturesHLSL.cpp +++ b/Engine/source/lighting/advanced/hlsl/deferredShadingFeaturesHLSL.cpp @@ -160,8 +160,8 @@ void DeferredSpecVarsHLSL::processPix( Vector &componentList, MultiLine * meta = new MultiLine; //matinfo.g slot reserved for AO later meta->addStatement(new GenOp(" @.g = 1.0;\r\n", material)); - meta->addStatement(new GenOp(" @.b = @/128;\r\n", material, specStrength)); - meta->addStatement(new GenOp(" @.a = @/5;\r\n", material, specPower)); + meta->addStatement(new GenOp(" @.a = @/128;\r\n", material, specPower)); + meta->addStatement(new GenOp(" @.b = @/5;\r\n", material, specStrength)); output = meta; } diff --git a/Engine/source/shaderGen/GLSL/shaderFeatureGLSL.cpp b/Engine/source/shaderGen/GLSL/shaderFeatureGLSL.cpp index cea0ca5db..78c45a09c 100644 --- a/Engine/source/shaderGen/GLSL/shaderFeatureGLSL.cpp +++ b/Engine/source/shaderGen/GLSL/shaderFeatureGLSL.cpp @@ -1856,7 +1856,7 @@ void ReflectCubeFeatGLSL::processPix( Vector &componentList, if (fd.features[MFT_DeferredSpecMap]) texCube = new GenOp("textureLod( @, @, (@.a*5) )", cubeMap, reflectVec, matinfo); else - texCube = new GenOp("textureLod( @, @, (@.a/4) )", cubeMap, reflectVec, matinfo); + texCube = new GenOp("textureLod( @, @, ((1.0-@.a)*6) )", cubeMap, reflectVec, matinfo); } else if(glossColor) //failing that, rtry and find color data texCube = new GenOp("textureLod( @, @, @.a*5)", cubeMap, reflectVec, glossColor); @@ -1896,7 +1896,7 @@ void ReflectCubeFeatGLSL::processPix( Vector &componentList, if (fd.features[MFT_DeferredSpecMap]) meta->addStatement(new GenOp(" @.rgb = lerp( @.rgb, (@).rgb, (@.b));\r\n", targ, targ, texCube, lerpVal)); else - meta->addStatement(new GenOp(" @.rgb = lerp( @.rgb, (@).rgb, (@.b*128/5));\r\n", targ, targ, texCube, lerpVal)); + meta->addStatement(new GenOp(" @.rgb = lerp( @.rgb, (@).rgb, (@.b));\r\n", targ, targ, texCube, lerpVal)); } else meta->addStatement( new GenOp( " @;\r\n", assignColor( texCube, blendOp, lerpVal ) ) ); diff --git a/Engine/source/shaderGen/HLSL/shaderFeatureHLSL.cpp b/Engine/source/shaderGen/HLSL/shaderFeatureHLSL.cpp index 0b40cfac3..2ab6a75f0 100644 --- a/Engine/source/shaderGen/HLSL/shaderFeatureHLSL.cpp +++ b/Engine/source/shaderGen/HLSL/shaderFeatureHLSL.cpp @@ -1857,7 +1857,7 @@ void ReflectCubeFeatHLSL::processPix( Vector &componentList, if (fd.features[MFT_DeferredSpecMap]) texCube = new GenOp("texCUBElod( @, float4(@, (@.a*5)) )", cubeMap, reflectVec, matinfo); else - texCube = new GenOp("texCUBElod( @, float4(@, (@.a/4)) )", cubeMap, reflectVec, matinfo); + texCube = new GenOp("texCUBElod( @, float4(@, ((1.0-@.a)*6)) )", cubeMap, reflectVec, matinfo); } else if (glossColor) //failing that, rtry and find color data @@ -1898,7 +1898,7 @@ void ReflectCubeFeatHLSL::processPix( Vector &componentList, if (fd.features[MFT_DeferredSpecMap]) meta->addStatement(new GenOp(" @.rgb = lerp( @.rgb, (@).rgb, (@.b));\r\n", targ, targ, texCube, lerpVal)); else - meta->addStatement(new GenOp(" @.rgb = lerp( @.rgb, (@).rgb, (@.b*128/5));\r\n", targ, targ, texCube, lerpVal)); + meta->addStatement(new GenOp(" @.rgb = lerp( @.rgb, (@).rgb, (@.b));\r\n", targ, targ, texCube, lerpVal)); } else meta->addStatement( new GenOp( " @;\r\n", assignColor( texCube, blendOp, lerpVal ) ) );