Torque3D/Engine/source/T3D/fx/precipitation.cpp
LuisAntonRebollo 1c95ce21d6 Merge pull request #608 from BeamNG/use_gfxdevice_setupgenericshaders
Use GFXDevice::setupGenericShaders for support non Fixed Fuction Pipelines.
2014-11-30 02:20:00 +01:00

1867 lines
55 KiB
C++

//-----------------------------------------------------------------------------
// 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 "T3D/fx/precipitation.h"
#include "math/mathIO.h"
#include "console/consoleTypes.h"
#include "console/typeValidators.h"
#include "scene/sceneManager.h"
#include "scene/sceneRenderState.h"
#include "lighting/lightInfo.h"
#include "lighting/lightManager.h"
#include "materials/shaderData.h"
#include "T3D/gameBase/gameConnection.h"
#include "T3D/player.h"
#include "core/stream/bitStream.h"
#include "platform/profiler.h"
#include "renderInstance/renderPassManager.h"
#include "sfx/sfxSystem.h"
#include "sfx/sfxTrack.h"
#include "sfx/sfxSource.h"
#include "sfx/sfxTypes.h"
#include "console/engineAPI.h"
#include "particleEmitter.h"
static const U32 dropHitMask =
TerrainObjectType |
WaterObjectType |
StaticShapeObjectType;
IMPLEMENT_CO_NETOBJECT_V1(Precipitation);
IMPLEMENT_CO_DATABLOCK_V1(PrecipitationData);
ConsoleDocClass( Precipitation,
"@brief Defines a precipitation based storm (rain, snow, etc).\n\n"
"The Precipitation effect works by creating many 'drops' within a fixed size "
"box. This box can be configured to move around with the camera (to simulate "
"level-wide precipitation), or to remain in a fixed position (to simulate "
"localized precipitation). When #followCam is true, the box containing the "
"droplets can be thought of as centered on the camera then pushed slightly "
"forward in the direction the camera is facing so most of the box is in "
"front of the camera (allowing more drops to be visible on screen at once).\n\n"
"The effect can also be configured to create a small 'splash' whenever a drop "
"hits another world object.\n\n"
"@tsexample\n"
"// The following is added to a level file (.mis) by the World Editor\n"
"new Precipitation( TheRain )\n"
"{\n"
" dropSize = \"0.5\";\n"
" splashSize = \"0.5\";\n"
" splashMS = \"250\";\n"
" animateSplashes = \"1\";\n"
" dropAnimateMS = \"0\";\n"
" fadeDist = \"0\";\n"
" fadeDistEnd = \"0\";\n"
" useTrueBillboards = \"0\";\n"
" useLighting = \"0\";\n"
" glowIntensity = \"0 0 0 0\";\n"
" reflect = \"0\";\n"
" rotateWithCamVel = \"1\";\n"
" doCollision = \"1\";\n"
" hitPlayers = \"0\";\n"
" hitVehicles = \"0\";\n"
" followCam = \"1\";\n"
" useWind = \"0\";\n"
" minSpeed = \"1.5\";\n"
" maxSpeed = \"2\";\n"
" minMass = \"0.75\";\n"
" maxMass = \"0.85\";\n"
" useTurbulence = \"0\";\n"
" maxTurbulence = \"0.1\";\n"
" turbulenceSpeed = \"0.2\";\n"
" numDrops = \"1024\";\n"
" boxWidth = \"200\";\n"
" boxHeight = \"100\";\n"
" dataBlock = \"HeavyRain\";\n"
"};\n"
"@endtsexample\n"
"@ingroup FX\n"
"@ingroup Atmosphere\n"
"@see PrecipitationData\n"
);
ConsoleDocClass( PrecipitationData,
"@brief Defines the droplets used in a storm (raindrops, snowflakes, etc).\n\n"
"@tsexample\n"
"datablock PrecipitationData( HeavyRain )\n"
"{\n"
" soundProfile = \"HeavyRainSound\";\n"
" dropTexture = \"art/environment/precipitation/rain\";\n"
" splashTexture = \"art/environment/precipitation/water_splash\";\n"
" dropsPerSide = 4;\n"
" splashesPerSide = 2;\n"
"};\n"
"@endtsexample\n"
"@ingroup FX\n"
"@ingroup Atmosphere\n"
"@see Precipitation\n"
);
//----------------------------------------------------------
// PrecipitationData
//----------------------------------------------------------
PrecipitationData::PrecipitationData()
{
soundProfile = NULL;
mDropName = StringTable->insert("");
mDropShaderName = StringTable->insert("");
mSplashName = StringTable->insert("");
mSplashShaderName = StringTable->insert("");
mDropsPerSide = 4;
mSplashesPerSide = 2;
}
void PrecipitationData::initPersistFields()
{
addField( "soundProfile", TYPEID< SFXTrack >(), Offset(soundProfile, PrecipitationData),
"Looping SFXProfile effect to play while Precipitation is active." );
addField( "dropTexture", TypeFilename, Offset(mDropName, PrecipitationData),
"@brief Texture filename for drop particles.\n\n"
"The drop texture can contain several different drop sub-textures "
"arranged in a grid. There must be the same number of rows as columns. A "
"random frame will be chosen for each drop." );
addField( "dropShader", TypeString, Offset(mDropShaderName, PrecipitationData),
"The name of the shader used for raindrops." );
addField( "splashTexture", TypeFilename, Offset(mSplashName, PrecipitationData),
"@brief Texture filename for splash particles.\n\n"
"The splash texture can contain several different splash sub-textures "
"arranged in a grid. There must be the same number of rows as columns. A "
"random frame will be chosen for each splash." );
addField( "splashShader", TypeString, Offset(mSplashShaderName, PrecipitationData),
"The name of the shader used for splashes." );
addField( "dropsPerSide", TypeS32, Offset(mDropsPerSide, PrecipitationData),
"@brief How many rows and columns are in the raindrop texture.\n\n"
"For example, if the texture has 16 raindrops arranged in a grid, this "
"field should be set to 4." );
addField( "splashesPerSide", TypeS32, Offset(mSplashesPerSide, PrecipitationData),
"@brief How many rows and columns are in the splash texture.\n\n"
"For example, if the texture has 9 splashes arranged in a grid, this "
"field should be set to 3." );
Parent::initPersistFields();
}
bool PrecipitationData::preload( bool server, String &errorStr )
{
if( Parent::preload( server, errorStr) == false)
return false;
if( !server && !sfxResolve( &soundProfile, errorStr ) )
return false;
return true;
}
void PrecipitationData::packData(BitStream* stream)
{
Parent::packData(stream);
sfxWrite( stream, soundProfile );
stream->writeString(mDropName);
stream->writeString(mDropShaderName);
stream->writeString(mSplashName);
stream->writeString(mSplashShaderName);
stream->write(mDropsPerSide);
stream->write(mSplashesPerSide);
}
void PrecipitationData::unpackData(BitStream* stream)
{
Parent::unpackData(stream);
sfxRead( stream, &soundProfile );
mDropName = stream->readSTString();
mDropShaderName = stream->readSTString();
mSplashName = stream->readSTString();
mSplashShaderName = stream->readSTString();
stream->read(&mDropsPerSide);
stream->read(&mSplashesPerSide);
}
//----------------------------------------------------------
// Precipitation!
//----------------------------------------------------------
Precipitation::Precipitation()
{
mTypeMask |= ProjectileObjectType;
mDataBlock = NULL;
mTexCoords = NULL;
mSplashCoords = NULL;
mDropShader = NULL;
mDropHandle = NULL;
mSplashShader = NULL;
mSplashHandle = NULL;
mDropHead = NULL;
mSplashHead = NULL;
mNumDrops = 1024;
mPercentage = 1.0;
mMinSpeed = 1.5;
mMaxSpeed = 2.0;
mFollowCam = true;
mLastRenderFrame = 0;
mDropHitMask = 0;
mDropSize = 0.5;
mSplashSize = 0.5;
mUseTrueBillboards = false;
mSplashMS = 250;
mAnimateSplashes = true;
mDropAnimateMS = 0;
mUseLighting = false;
mGlowIntensity = ColorF( 0,0,0,0 );
mReflect = false;
mUseWind = false;
mBoxWidth = 200;
mBoxHeight = 100;
mFadeDistance = 0;
mFadeDistanceEnd = 0;
mMinMass = 0.75f;
mMaxMass = 0.85f;
mMaxTurbulence = 0.1f;
mTurbulenceSpeed = 0.2f;
mUseTurbulence = false;
mRotateWithCamVel = true;
mDoCollision = true;
mDropHitPlayers = false;
mDropHitVehicles = false;
mStormData.valid = false;
mStormData.startPct = 0;
mStormData.endPct = 0;
mStormData.startTime = 0;
mStormData.totalTime = 0;
mTurbulenceData.valid = false;
mTurbulenceData.startTime = 0;
mTurbulenceData.totalTime = 0;
mTurbulenceData.startMax = 0;
mTurbulenceData.startSpeed = 0;
mTurbulenceData.endMax = 0;
mTurbulenceData.endSpeed = 0;
mAmbientSound = NULL;
mDropShaderModelViewSC = NULL;
mDropShaderFadeStartEndSC = NULL;
mDropShaderCameraPosSC = NULL;
mDropShaderAmbientSC = NULL;
mSplashShaderModelViewSC = NULL;
mSplashShaderFadeStartEndSC = NULL;
mSplashShaderCameraPosSC = NULL;
mSplashShaderAmbientSC = NULL;
}
Precipitation::~Precipitation()
{
SAFE_DELETE_ARRAY(mTexCoords);
SAFE_DELETE_ARRAY(mSplashCoords);
}
void Precipitation::inspectPostApply()
{
if (mFollowCam)
{
setGlobalBounds();
}
else
{
mObjBox.minExtents = -Point3F(mBoxWidth/2, mBoxWidth/2, mBoxHeight/2);
mObjBox.maxExtents = Point3F(mBoxWidth/2, mBoxWidth/2, mBoxHeight/2);
}
resetWorldBox();
setMaskBits(DataMask);
}
void Precipitation::setTransform(const MatrixF & mat)
{
Parent::setTransform(mat);
setMaskBits(TransformMask);
}
//--------------------------------------------------------------------------
// Console stuff...
//--------------------------------------------------------------------------
IRangeValidator ValidNumDropsRange(1, 100000);
void Precipitation::initPersistFields()
{
addGroup("Precipitation");
addFieldV( "numDrops", TypeS32, Offset(mNumDrops, Precipitation), &ValidNumDropsRange,
"@brief Maximum number of drops allowed to exist in the precipitation "
"box at any one time.\n\n"
"The actual number of drops in the effect depends on the current "
"percentage, which can change over time using modifyStorm()." );
addField( "boxWidth", TypeF32, Offset(mBoxWidth, Precipitation),
"Width and depth (horizontal dimensions) of the precipitation box." );
addField( "boxHeight", TypeF32, Offset(mBoxHeight, Precipitation),
"Height (vertical dimension) of the precipitation box." );
endGroup("Precipitation");
addGroup("Rendering");
addField( "dropSize", TypeF32, Offset(mDropSize, Precipitation),
"Size of each drop of precipitation. This will scale the texture." );
addField( "splashSize", TypeF32, Offset(mSplashSize, Precipitation),
"Size of each splash animation when a drop collides with another surface." );
addField( "splashMS", TypeS32, Offset(mSplashMS, Precipitation),
"Lifetime of splashes in milliseconds." );
addField( "animateSplashes", TypeBool, Offset(mAnimateSplashes, Precipitation),
"Set to true to enable splash animations when drops collide with other surfaces." );
addField( "dropAnimateMS", TypeS32, Offset(mDropAnimateMS, Precipitation),
"@brief Length (in milliseconds) to display each drop frame.\n\n"
"If #dropAnimateMS <= 0, drops select a single random frame at creation "
"that does not change throughout the drop's lifetime. If #dropAnimateMS "
"> 0, each drop cycles through the the available frames in the drop "
"texture at the given rate." );
addField( "fadeDist", TypeF32, Offset(mFadeDistance, Precipitation),
"The distance at which drops begin to fade out." );
addField( "fadeDistEnd", TypeF32, Offset(mFadeDistanceEnd, Precipitation),
"The distance at which drops are completely faded out." );
addField( "useTrueBillboards", TypeBool, Offset(mUseTrueBillboards, Precipitation),
"Set to true to make drops true (non axis-aligned) billboards." );
addField( "useLighting", TypeBool, Offset(mUseLighting, Precipitation),
"Set to true to enable shading of the drops and splashes by the sun color." );
addField( "glowIntensity", TypeColorF, Offset(mGlowIntensity, Precipitation),
"Set to 0 to disable the glow or or use it to control the intensity of each channel." );
addField( "reflect", TypeBool, Offset(mReflect, Precipitation),
"@brief This enables precipitation rendering during reflection passes.\n\n"
"@note This is expensive." );
addField( "rotateWithCamVel", TypeBool, Offset(mRotateWithCamVel, Precipitation),
"Set to true to include the camera velocity when calculating drop "
"rotation speed." );
endGroup("Rendering");
addGroup("Collision");
addField( "doCollision", TypeBool, Offset(mDoCollision, Precipitation),
"@brief Allow drops to collide with world objects.\n\n"
"If #animateSplashes is true, drops that collide with another object "
"will produce a simple splash animation.\n"
"@note This can be expensive as each drop will perform a raycast when "
"it is created to determine where it will hit." );
addField( "hitPlayers", TypeBool, Offset(mDropHitPlayers, Precipitation),
"Allow drops to collide with Player objects; only valid if #doCollision is true." );
addField( "hitVehicles", TypeBool, Offset(mDropHitVehicles, Precipitation),
"Allow drops to collide with Vehicle objects; only valid if #doCollision is true." );
endGroup("Collision");
addGroup("Movement");
addField( "followCam", TypeBool, Offset(mFollowCam, Precipitation),
"@brief Controls whether the Precipitation system follows the camera "
"or remains where it is first placed in the scene.\n\n"
"Set to true to make it seem like it is raining everywhere in the "
"level (ie. the Player will always be in the rain). Set to false "
"to have a single area affected by rain (ie. the Player can move in "
"and out of the rainy area)." );
addField( "useWind", TypeBool, Offset(mUseWind, Precipitation),
"Controls whether drops are affected by wind.\n"
"@see ForestWindEmitter" );
addField( "minSpeed", TypeF32, Offset(mMinSpeed, Precipitation),
"@brief Minimum speed at which a drop will fall.\n\n"
"On creation, the drop will be assigned a random speed between #minSpeed "
"and #maxSpeed." );
addField( "maxSpeed", TypeF32, Offset(mMaxSpeed, Precipitation),
"@brief Maximum speed at which a drop will fall.\n\n"
"On creation, the drop will be assigned a random speed between #minSpeed "
"and #maxSpeed." );
addField( "minMass", TypeF32, Offset(mMinMass, Precipitation),
"@brief Minimum mass of a drop.\n\n"
"Drop mass determines how strongly the drop is affected by wind and "
"turbulence. On creation, the drop will be assigned a random speed "
"between #minMass and #minMass." );
addField( "maxMass", TypeF32, Offset(mMaxMass, Precipitation),
"@brief Maximum mass of a drop.\n\n"
"Drop mass determines how strongly the drop is affected by wind and "
"turbulence. On creation, the drop will be assigned a random speed "
"between #minMass and #minMass." );
endGroup("Movement");
addGroup("Turbulence");
addField( "useTurbulence", TypeBool, Offset(mUseTurbulence, Precipitation),
"Check to enable turbulence. This causes precipitation drops to spiral "
"while falling." );
addField( "maxTurbulence", TypeF32, Offset(mMaxTurbulence, Precipitation),
"Radius at which precipitation drops spiral when turbulence is enabled." );
addField( "turbulenceSpeed", TypeF32, Offset(mTurbulenceSpeed, Precipitation),
"Speed at which precipitation drops spiral when turbulence is enabled." );
endGroup("Turbulence");
Parent::initPersistFields();
}
//-----------------------------------
// Console methods...
DefineEngineMethod(Precipitation, setPercentage, void, (F32 percentage), (1.0f),
"Sets the maximum number of drops in the effect, as a percentage of #numDrops.\n"
"The change occurs instantly (use modifyStorm() to change the number of drops "
"over a period of time.\n"
"@param percentage New maximum number of drops value (as a percentage of "
"#numDrops). Valid range is 0-1.\n"
"@tsexample\n"
"%percentage = 0.5; // The percentage, from 0 to 1, of the maximum drops to display\n"
"%precipitation.setPercentage( %percentage );\n"
"@endtsexample\n"
"@see modifyStorm\n" )
{
object->setPercentage(percentage);
}
DefineEngineMethod(Precipitation, modifyStorm, void, (F32 percentage, F32 seconds), (1.0f, 5.0f),
"Smoothly change the maximum number of drops in the effect (from current "
"value to #numDrops * @a percentage).\n"
"This method can be used to simulate a storm building or fading in intensity "
"as the number of drops in the Precipitation box changes.\n"
"@param percentage New maximum number of drops value (as a percentage of "
"#numDrops). Valid range is 0-1.\n"
"@param seconds Length of time (in seconds) over which to increase the drops "
"percentage value. Set to 0 to change instantly.\n"
"@tsexample\n"
"%percentage = 0.5; // The percentage, from 0 to 1, of the maximum drops to display\n"
"%seconds = 5.0; // The length of time over which to make the change.\n"
"%precipitation.modifyStorm( %percentage, %seconds );\n"
"@endtsexample\n" )
{
object->modifyStorm(percentage, S32(seconds * 1000.0f));
}
DefineEngineMethod(Precipitation, setTurbulence, void, (F32 max, F32 speed, F32 seconds), (1.0f, 5.0f, 5.0),
"Smoothly change the turbulence parameters over a period of time.\n"
"@param max New #maxTurbulence value. Set to 0 to disable turbulence.\n"
"@param speed New #turbulenceSpeed value.\n"
"@param seconds Length of time (in seconds) over which to interpolate the "
"turbulence settings. Set to 0 to change instantly.\n"
"@tsexample\n"
"%turbulence = 0.5; // Set the new turbulence value. Set to 0 to disable turbulence.\n"
"%speed = 5.0; // The new speed of the turbulance effect.\n"
"%seconds = 5.0; // The length of time over which to make the change.\n"
"%precipitation.setTurbulence( %turbulence, %speed, %seconds );\n"
"@endtsexample\n" )
{
object->setTurbulence( max, speed, S32(seconds * 1000.0f));
}
//--------------------------------------------------------------------------
// Backend
//--------------------------------------------------------------------------
bool Precipitation::onAdd()
{
if(!Parent::onAdd())
return false;
if (mFollowCam)
{
setGlobalBounds();
}
else
{
mObjBox.minExtents = -Point3F(mBoxWidth/2, mBoxWidth/2, mBoxHeight/2);
mObjBox.maxExtents = Point3F(mBoxWidth/2, mBoxWidth/2, mBoxHeight/2);
}
resetWorldBox();
if (isClientObject())
{
fillDropList();
initRenderObjects();
initMaterials();
}
addToScene();
return true;
}
void Precipitation::onRemove()
{
removeFromScene();
Parent::onRemove();
SFX_DELETE( mAmbientSound );
if (isClientObject())
killDropList();
}
bool Precipitation::onNewDataBlock( GameBaseData *dptr, bool reload )
{
mDataBlock = dynamic_cast<PrecipitationData*>( dptr );
if ( !mDataBlock || !Parent::onNewDataBlock( dptr, reload ) )
return false;
if (isClientObject())
{
SFX_DELETE( mAmbientSound );
if ( mDataBlock->soundProfile )
{
mAmbientSound = SFX->createSource( mDataBlock->soundProfile, &getTransform() );
if ( mAmbientSound )
mAmbientSound->play();
}
initRenderObjects();
initMaterials();
}
scriptOnNewDataBlock();
return true;
}
void Precipitation::initMaterials()
{
AssertFatal(isClientObject(), "Precipitation is setting materials on the server - BAD!");
if(!mDataBlock)
return;
PrecipitationData *pd = (PrecipitationData*)mDataBlock;
mDropHandle = NULL;
mSplashHandle = NULL;
mDropShader = NULL;
mSplashShader = NULL;
if( dStrlen(pd->mDropName) > 0 && !mDropHandle.set(pd->mDropName, &GFXDefaultStaticDiffuseProfile, avar("%s() - mDropHandle (line %d)", __FUNCTION__, __LINE__)) )
Con::warnf("Precipitation::initMaterials - failed to locate texture '%s'!", pd->mDropName);
if ( dStrlen(pd->mDropShaderName) > 0 )
{
ShaderData *shaderData;
if ( Sim::findObject( pd->mDropShaderName, shaderData ) )
mDropShader = shaderData->getShader();
if( !mDropShader )
Con::warnf( "Precipitation::initMaterials - could not find shader '%s'!", pd->mDropShaderName );
else
{
mDropShaderConsts = mDropShader->allocConstBuffer();
mDropShaderModelViewSC = mDropShader->getShaderConstHandle("$modelView");
mDropShaderFadeStartEndSC = mDropShader->getShaderConstHandle("$fadeStartEnd");
mDropShaderCameraPosSC = mDropShader->getShaderConstHandle("$cameraPos");
mDropShaderAmbientSC = mDropShader->getShaderConstHandle("$ambient");
}
}
if( dStrlen(pd->mSplashName) > 0 && !mSplashHandle.set(pd->mSplashName, &GFXDefaultStaticDiffuseProfile, avar("%s() - mSplashHandle (line %d)", __FUNCTION__, __LINE__)) )
Con::warnf("Precipitation::initMaterials - failed to locate texture '%s'!", pd->mSplashName);
if ( dStrlen(pd->mSplashShaderName) > 0 )
{
ShaderData *shaderData;
if ( Sim::findObject( pd->mSplashShaderName, shaderData ) )
mSplashShader = shaderData->getShader();
if( !mSplashShader )
Con::warnf( "Precipitation::initMaterials - could not find shader '%s'!", pd->mSplashShaderName );
else
{
mSplashShaderConsts = mSplashShader->allocConstBuffer();
mSplashShaderModelViewSC = mSplashShader->getShaderConstHandle("$modelView");
mSplashShaderFadeStartEndSC = mSplashShader->getShaderConstHandle("$fadeStartEnd");
mSplashShaderCameraPosSC = mSplashShader->getShaderConstHandle("$cameraPos");
mSplashShaderAmbientSC = mSplashShader->getShaderConstHandle("$ambient");
}
}
}
U32 Precipitation::packUpdate(NetConnection* con, U32 mask, BitStream* stream)
{
Parent::packUpdate(con, mask, stream);
if (stream->writeFlag( !mFollowCam && mask & TransformMask))
stream->writeAffineTransform(mObjToWorld);
if (stream->writeFlag(mask & DataMask))
{
stream->write(mDropSize);
stream->write(mSplashSize);
stream->write(mSplashMS);
stream->write(mDropAnimateMS);
stream->write(mNumDrops);
stream->write(mMinSpeed);
stream->write(mMaxSpeed);
stream->write(mBoxWidth);
stream->write(mBoxHeight);
stream->write(mMinMass);
stream->write(mMaxMass);
stream->write(mMaxTurbulence);
stream->write(mTurbulenceSpeed);
stream->write(mFadeDistance);
stream->write(mFadeDistanceEnd);
stream->write(mGlowIntensity.red);
stream->write(mGlowIntensity.green);
stream->write(mGlowIntensity.blue);
stream->write(mGlowIntensity.alpha);
stream->writeFlag(mReflect);
stream->writeFlag(mRotateWithCamVel);
stream->writeFlag(mDoCollision);
stream->writeFlag(mDropHitPlayers);
stream->writeFlag(mDropHitVehicles);
stream->writeFlag(mUseTrueBillboards);
stream->writeFlag(mUseTurbulence);
stream->writeFlag(mUseLighting);
stream->writeFlag(mUseWind);
stream->writeFlag(mFollowCam);
stream->writeFlag(mAnimateSplashes);
}
if (stream->writeFlag(!(mask & DataMask) && (mask & TurbulenceMask)))
{
stream->write(mTurbulenceData.endMax);
stream->write(mTurbulenceData.endSpeed);
stream->write(mTurbulenceData.totalTime);
}
if (stream->writeFlag(mask & PercentageMask))
{
stream->write(mPercentage);
}
if (stream->writeFlag(!(mask & ~(DataMask | PercentageMask | StormMask)) && (mask & StormMask)))
{
stream->write(mStormData.endPct);
stream->write(mStormData.totalTime);
}
return 0;
}
void Precipitation::unpackUpdate(NetConnection* con, BitStream* stream)
{
Parent::unpackUpdate(con, stream);
if (stream->readFlag())
{
MatrixF mat;
stream->readAffineTransform(&mat);
Parent::setTransform(mat);
}
U32 oldDrops = U32(mNumDrops * mPercentage);
if (stream->readFlag())
{
stream->read(&mDropSize);
stream->read(&mSplashSize);
stream->read(&mSplashMS);
stream->read(&mDropAnimateMS);
stream->read(&mNumDrops);
stream->read(&mMinSpeed);
stream->read(&mMaxSpeed);
stream->read(&mBoxWidth);
stream->read(&mBoxHeight);
stream->read(&mMinMass);
stream->read(&mMaxMass);
stream->read(&mMaxTurbulence);
stream->read(&mTurbulenceSpeed);
stream->read(&mFadeDistance);
stream->read(&mFadeDistanceEnd);
stream->read(&mGlowIntensity.red);
stream->read(&mGlowIntensity.green);
stream->read(&mGlowIntensity.blue);
stream->read(&mGlowIntensity.alpha);
mReflect = stream->readFlag();
mRotateWithCamVel = stream->readFlag();
mDoCollision = stream->readFlag();
mDropHitPlayers = stream->readFlag();
mDropHitVehicles = stream->readFlag();
mUseTrueBillboards = stream->readFlag();
mUseTurbulence = stream->readFlag();
mUseLighting = stream->readFlag();
mUseWind = stream->readFlag();
mFollowCam = stream->readFlag();
mAnimateSplashes = stream->readFlag();
mDropHitMask = dropHitMask |
( mDropHitPlayers ? PlayerObjectType : 0 ) |
( mDropHitVehicles ? VehicleObjectType : 0 );
mTurbulenceData.valid = false;
}
if (stream->readFlag())
{
F32 max, speed;
U32 ms;
stream->read(&max);
stream->read(&speed);
stream->read(&ms);
setTurbulence( max, speed, ms );
}
if (stream->readFlag())
{
F32 pct;
stream->read(&pct);
setPercentage(pct);
}
if (stream->readFlag())
{
F32 pct;
U32 time;
stream->read(&pct);
stream->read(&time);
modifyStorm(pct, time);
}
AssertFatal(isClientObject(), "Precipitation::unpackUpdate() should only be called on the client!");
U32 newDrops = U32(mNumDrops * mPercentage);
if (oldDrops != newDrops)
{
fillDropList();
initRenderObjects();
}
if (mFollowCam)
{
setGlobalBounds();
}
else
{
mObjBox.minExtents = -Point3F(mBoxWidth/2, mBoxWidth/2, mBoxHeight/2);
mObjBox.maxExtents = Point3F(mBoxWidth/2, mBoxWidth/2, mBoxHeight/2);
}
resetWorldBox();
}
//--------------------------------------------------------------------------
// Support functions
//--------------------------------------------------------------------------
VectorF Precipitation::getWindVelocity()
{
// The WindManager happens to set global-wind velocity here, it is not just for particles.
return mUseWind ? ParticleEmitter::mWindVelocity : Point3F::Zero;
}
void Precipitation::fillDropList()
{
AssertFatal(isClientObject(), "Precipitation is doing stuff on the server - BAD!");
F32 density = Con::getFloatVariable("$pref::precipitationDensity", 1.0f);
U32 newDropCount = (U32)(mNumDrops * mPercentage * density);
U32 dropCount = 0;
if (newDropCount == 0)
killDropList();
if (mDropHead)
{
Raindrop* curr = mDropHead;
while (curr)
{
dropCount++;
curr = curr->next;
if (dropCount == newDropCount && curr)
{
//delete the remaining drops
Raindrop* next = curr->next;
curr->next = NULL;
while (next)
{
Raindrop* last = next;
next = next->next;
last->next = NULL;
destroySplash(last);
delete last;
}
break;
}
}
}
if (dropCount < newDropCount)
{
//move to the end
Raindrop* curr = mDropHead;
if (curr)
{
while (curr->next)
curr = curr->next;
}
else
{
mDropHead = curr = new Raindrop;
spawnNewDrop(curr);
dropCount++;
}
//and add onto it
while (dropCount < newDropCount)
{
curr->next = new Raindrop;
curr = curr->next;
spawnNewDrop(curr);
dropCount++;
}
}
}
void Precipitation::initRenderObjects()
{
AssertFatal(isClientObject(), "Precipitation is doing stuff on the server - BAD!");
SAFE_DELETE_ARRAY(mTexCoords);
SAFE_DELETE_ARRAY(mSplashCoords);
if (!mDataBlock)
return;
mTexCoords = new Point2F[4*mDataBlock->mDropsPerSide*mDataBlock->mDropsPerSide];
// Setup the texcoords for the drop texture.
// The order of the coords when animating is...
//
// +---+---+---+
// | 1 | 2 | 3 |
// |---|---|---+
// | 4 | 5 | 6 |
// +---+---+---+
// | 7 | etc...
// +---+
//
U32 count = 0;
for (U32 v = 0; v < mDataBlock->mDropsPerSide; v++)
{
F32 y1 = (F32) v / mDataBlock->mDropsPerSide;
F32 y2 = (F32)(v+1) / mDataBlock->mDropsPerSide;
for (U32 u = 0; u < mDataBlock->mDropsPerSide; u++)
{
F32 x1 = (F32) u / mDataBlock->mDropsPerSide;
F32 x2 = (F32)(u+1) / mDataBlock->mDropsPerSide;
mTexCoords[4*count+0].x = x1;
mTexCoords[4*count+0].y = y1;
mTexCoords[4*count+1].x = x2;
mTexCoords[4*count+1].y = y1;
mTexCoords[4*count+2].x = x2;
mTexCoords[4*count+2].y = y2;
mTexCoords[4*count+3].x = x1;
mTexCoords[4*count+3].y = y2;
count++;
}
}
count = 0;
mSplashCoords = new Point2F[4*mDataBlock->mSplashesPerSide*mDataBlock->mSplashesPerSide];
for (U32 v = 0; v < mDataBlock->mSplashesPerSide; v++)
{
F32 y1 = (F32) v / mDataBlock->mSplashesPerSide;
F32 y2 = (F32)(v+1) / mDataBlock->mSplashesPerSide;
for (U32 u = 0; u < mDataBlock->mSplashesPerSide; u++)
{
F32 x1 = (F32) u / mDataBlock->mSplashesPerSide;
F32 x2 = (F32)(u+1) / mDataBlock->mSplashesPerSide;
mSplashCoords[4*count+0].x = x1;
mSplashCoords[4*count+0].y = y1;
mSplashCoords[4*count+1].x = x2;
mSplashCoords[4*count+1].y = y1;
mSplashCoords[4*count+2].x = x2;
mSplashCoords[4*count+2].y = y2;
mSplashCoords[4*count+3].x = x1;
mSplashCoords[4*count+3].y = y2;
count++;
}
}
// Cap the number of precipitation drops so that we don't blow out the max verts
mMaxVBDrops = getMin( (U32)mNumDrops, ( GFX->getMaxDynamicVerts() / 4 ) - 1 );
// If we have no drops then skip allocating anything!
if ( mMaxVBDrops == 0 )
return;
// Create a volitile vertex buffer which
// we'll lock and fill every frame.
mRainVB.set(GFX, mMaxVBDrops * 4, GFXBufferTypeVolatile);
// Init the index buffer for rendering the
// entire or a partially filled vb.
mRainIB.set(GFX, mMaxVBDrops * 6, 0, GFXBufferTypeStatic);
U16 *idxBuff;
mRainIB.lock(&idxBuff, NULL, NULL, NULL);
for( U32 i=0; i < mMaxVBDrops; i++ )
{
//
// The vertex pattern in the VB for each
// particle is as follows...
//
// 0----1
// |\ |
// | \ |
// | \ |
// | \|
// 3----2
//
// We setup the index order below to ensure
// sequential, cache friendly, access.
//
U32 offset = i * 4;
idxBuff[i*6+0] = 0 + offset;
idxBuff[i*6+1] = 1 + offset;
idxBuff[i*6+2] = 2 + offset;
idxBuff[i*6+3] = 2 + offset;
idxBuff[i*6+4] = 3 + offset;
idxBuff[i*6+5] = 0 + offset;
}
mRainIB.unlock();
}
void Precipitation::killDropList()
{
AssertFatal(isClientObject(), "Precipitation is doing stuff on the server - BAD!");
Raindrop* curr = mDropHead;
while (curr)
{
Raindrop* next = curr->next;
delete curr;
curr = next;
}
mDropHead = NULL;
mSplashHead = NULL;
}
void Precipitation::spawnDrop(Raindrop *drop)
{
PROFILE_START(PrecipSpawnDrop);
AssertFatal(isClientObject(), "Precipitation is doing stuff on the server - BAD!");
drop->velocity = Platform::getRandom() * (mMaxSpeed - mMinSpeed) + mMinSpeed;
drop->position.x = Platform::getRandom() * mBoxWidth;
drop->position.y = Platform::getRandom() * mBoxWidth;
// The start time should be randomized so that
// all the drops are not animating at the same time.
drop->animStartTime = (SimTime)(Platform::getVirtualMilliseconds() * Platform::getRandom());
if (mDropAnimateMS <= 0 && mDataBlock)
drop->texCoordIndex = (U32)(Platform::getRandom() * ((F32)mDataBlock->mDropsPerSide*mDataBlock->mDropsPerSide - 0.5));
drop->valid = true;
drop->time = Platform::getRandom() * M_2PI;
drop->mass = Platform::getRandom() * (mMaxMass - mMinMass) + mMinMass;
PROFILE_END();
}
void Precipitation::spawnNewDrop(Raindrop *drop)
{
AssertFatal(isClientObject(), "Precipitation is doing stuff on the server - BAD!");
spawnDrop(drop);
drop->position.z = Platform::getRandom() * mBoxHeight - (mBoxHeight / 2);
}
void Precipitation::wrapDrop(Raindrop *drop, const Box3F &box, const U32 currTime, const VectorF &windVel)
{
//could probably be slightly optimized to get rid of the while loops
if (drop->position.z < box.minExtents.z)
{
spawnDrop(drop);
drop->position.x += box.minExtents.x;
drop->position.y += box.minExtents.y;
while (drop->position.z < box.minExtents.z)
drop->position.z += mBoxHeight;
findDropCutoff(drop, box, windVel);
}
else if (drop->position.z > box.maxExtents.z)
{
while (drop->position.z > box.maxExtents.z)
drop->position.z -= mBoxHeight;
findDropCutoff(drop, box, windVel);
}
else if (drop->position.x < box.minExtents.x)
{
while (drop->position.x < box.minExtents.x)
drop->position.x += mBoxWidth;
findDropCutoff(drop, box, windVel);
}
else if (drop->position.x > box.maxExtents.x)
{
while (drop->position.x > box.maxExtents.x)
drop->position.x -= mBoxWidth;
findDropCutoff(drop, box, windVel);
}
else if (drop->position.y < box.minExtents.y)
{
while (drop->position.y < box.minExtents.y)
drop->position.y += mBoxWidth;
findDropCutoff(drop, box, windVel);
}
else if (drop->position.y > box.maxExtents.y)
{
while (drop->position.y > box.maxExtents.y)
drop->position.y -= mBoxWidth;
findDropCutoff(drop, box, windVel);
}
}
void Precipitation::findDropCutoff(Raindrop *drop, const Box3F &box, const VectorF &windVel)
{
PROFILE_START(PrecipFindDropCutoff);
AssertFatal(isClientObject(), "Precipitation is doing stuff on the server - BAD!");
if (mDoCollision)
{
VectorF velocity = windVel / drop->mass - VectorF(0, 0, drop->velocity);
velocity.normalize();
Point3F end = drop->position + 100 * velocity;
Point3F start = drop->position - (mFollowCam ? 500.0f : 0.0f) * velocity;
if (!mFollowCam)
{
mObjToWorld.mulP(start);
mObjToWorld.mulP(end);
}
// Look for a collision... make sure we don't
// collide with backfaces.
RayInfo rInfo;
if (getContainer()->castRay(start, end, mDropHitMask, &rInfo))
{
// TODO: Add check to filter out hits on backfaces.
if (!mFollowCam)
mWorldToObj.mulP(rInfo.point);
drop->hitPos = rInfo.point;
drop->hitType = rInfo.object->getTypeMask();
}
else
drop->hitPos = Point3F(0,0,-1000);
drop->valid = drop->position.z > drop->hitPos.z;
}
else
{
drop->hitPos = Point3F(0,0,-1000);
drop->valid = true;
}
PROFILE_END();
}
void Precipitation::createSplash(Raindrop *drop)
{
if (!mDataBlock)
return;
PROFILE_START(PrecipCreateSplash);
if (drop != mSplashHead && !(drop->nextSplashDrop || drop->prevSplashDrop))
{
if (!mSplashHead)
{
mSplashHead = drop;
drop->prevSplashDrop = NULL;
drop->nextSplashDrop = NULL;
}
else
{
mSplashHead->prevSplashDrop = drop;
drop->nextSplashDrop = mSplashHead;
drop->prevSplashDrop = NULL;
mSplashHead = drop;
}
}
drop->animStartTime = Platform::getVirtualMilliseconds();
if (!mAnimateSplashes)
drop->texCoordIndex = (U32)(Platform::getRandom() * ((F32)mDataBlock->mSplashesPerSide*mDataBlock->mSplashesPerSide - 0.5));
PROFILE_END();
}
void Precipitation::destroySplash(Raindrop *drop)
{
PROFILE_START(PrecipDestroySplash);
if (drop == mSplashHead)
{
mSplashHead = mSplashHead->nextSplashDrop;
}
if (drop->nextSplashDrop)
drop->nextSplashDrop->prevSplashDrop = drop->prevSplashDrop;
if (drop->prevSplashDrop)
drop->prevSplashDrop->nextSplashDrop = drop->nextSplashDrop;
drop->nextSplashDrop = NULL;
drop->prevSplashDrop = NULL;
PROFILE_END();
}
//--------------------------------------------------------------------------
// Processing
//--------------------------------------------------------------------------
void Precipitation::setPercentage(F32 pct)
{
mPercentage = mClampF(pct, 0, 1);
mStormData.valid = false;
if (isServerObject())
{
setMaskBits(PercentageMask);
}
}
void Precipitation::modifyStorm(F32 pct, U32 ms)
{
if ( ms == 0 )
{
setPercentage( pct );
return;
}
pct = mClampF(pct, 0, 1);
mStormData.endPct = pct;
mStormData.totalTime = ms;
if (isServerObject())
{
setMaskBits(StormMask);
return;
}
mStormData.startTime = Platform::getVirtualMilliseconds();
mStormData.startPct = mPercentage;
mStormData.valid = true;
}
void Precipitation::setTurbulence(F32 max, F32 speed, U32 ms)
{
if ( ms == 0 && !isServerObject() )
{
mUseTurbulence = max > 0;
mMaxTurbulence = max;
mTurbulenceSpeed = speed;
return;
}
mTurbulenceData.endMax = max;
mTurbulenceData.endSpeed = speed;
mTurbulenceData.totalTime = ms;
if (isServerObject())
{
setMaskBits(TurbulenceMask);
return;
}
mTurbulenceData.startTime = Platform::getVirtualMilliseconds();
mTurbulenceData.startMax = mMaxTurbulence;
mTurbulenceData.startSpeed = mTurbulenceSpeed;
mTurbulenceData.valid = true;
}
void Precipitation::interpolateTick(F32 delta)
{
AssertFatal(isClientObject(), "Precipitation is doing stuff on the server - BAD!");
// If we're not being seen then the simulation
// is paused and we don't need any interpolation.
if (mLastRenderFrame != ShapeBase::sLastRenderFrame)
return;
PROFILE_START(PrecipInterpolate);
const F32 dt = 1-delta;
const VectorF windVel = dt * getWindVelocity();
const F32 turbSpeed = dt * mTurbulenceSpeed;
Raindrop* curr = mDropHead;
VectorF turbulence;
F32 renderTime;
while (curr)
{
if (!curr->valid || !curr->toRender)
{
curr = curr->next;
continue;
}
if (mUseTurbulence)
{
renderTime = curr->time + turbSpeed;
turbulence.x = windVel.x + ( mSin(renderTime) * mMaxTurbulence );
turbulence.y = windVel.y + ( mCos(renderTime) * mMaxTurbulence );
turbulence.z = windVel.z;
curr->renderPosition = curr->position + turbulence / curr->mass;
}
else
curr->renderPosition = curr->position + windVel / curr->mass;
curr->renderPosition.z -= dt * curr->velocity;
curr = curr->next;
}
PROFILE_END();
}
void Precipitation::processTick(const Move *)
{
//nothing to do on the server
if (isServerObject() || mDataBlock == NULL)
return;
const U32 currTime = Platform::getVirtualMilliseconds();
// Update the storm if necessary
if (mStormData.valid)
{
F32 t = (currTime - mStormData.startTime) / (F32)mStormData.totalTime;
if (t >= 1)
{
mPercentage = mStormData.endPct;
mStormData.valid = false;
}
else
mPercentage = mStormData.startPct * (1-t) + mStormData.endPct * t;
fillDropList();
}
// Do we need to update the turbulence?
if ( mTurbulenceData.valid )
{
F32 t = (currTime - mTurbulenceData.startTime) / (F32)mTurbulenceData.totalTime;
if (t >= 1)
{
mMaxTurbulence = mTurbulenceData.endMax;
mTurbulenceSpeed = mTurbulenceData.endSpeed;
mTurbulenceData.valid = false;
}
else
{
mMaxTurbulence = mTurbulenceData.startMax * (1-t) + mTurbulenceData.endMax * t;
mTurbulenceSpeed = mTurbulenceData.startSpeed * (1-t) + mTurbulenceData.endSpeed * t;
}
mUseTurbulence = mMaxTurbulence > 0;
}
// If we're not being seen then pause the
// simulation. Precip is generally noisy
// enough that no one should notice.
if (mLastRenderFrame != ShapeBase::sLastRenderFrame)
return;
//we need to update positions and do some collision here
GameConnection* conn = GameConnection::getConnectionToServer();
if (!conn)
return; //need connection to server
ShapeBase* camObj = dynamic_cast<ShapeBase*>(conn->getCameraObject());
if (!camObj)
return;
PROFILE_START(PrecipProcess);
MatrixF camMat;
camObj->getEyeTransform(&camMat);
const F32 camFov = camObj->getCameraFov();
Point3F camPos, camDir;
Box3F box;
if (mFollowCam)
{
camMat.getColumn(3, &camPos);
box = Box3F(camPos.x - mBoxWidth / 2, camPos.y - mBoxWidth / 2, camPos.z - mBoxHeight / 2,
camPos.x + mBoxWidth / 2, camPos.y + mBoxWidth / 2, camPos.z + mBoxHeight / 2);
camMat.getColumn(1, &camDir);
camDir.normalize();
}
else
{
box = mObjBox;
camMat.getColumn(3, &camPos);
mWorldToObj.mulP(camPos);
camMat.getColumn(1, &camDir);
camDir.normalize();
mWorldToObj.mulV(camDir);
}
const VectorF windVel = getWindVelocity();
const F32 fovDot = camFov / 180;
Raindrop* curr = mDropHead;
//offset the renderbox in the direction of the camera direction
//in order to have more of the drops actually rendered
if (mFollowCam)
{
box.minExtents.x += camDir.x * mBoxWidth / 4;
box.maxExtents.x += camDir.x * mBoxWidth / 4;
box.minExtents.y += camDir.y * mBoxWidth / 4;
box.maxExtents.y += camDir.y * mBoxWidth / 4;
box.minExtents.z += camDir.z * mBoxHeight / 4;
box.maxExtents.z += camDir.z * mBoxHeight / 4;
}
VectorF lookVec;
F32 pct;
const S32 dropCount = mDataBlock->mDropsPerSide*mDataBlock->mDropsPerSide;
while (curr)
{
// Update the position. This happens even if this
// is a splash so that the drop respawns when it wraps
// around to the top again.
if (mUseTurbulence)
curr->time += mTurbulenceSpeed;
curr->position += windVel / curr->mass;
curr->position.z -= curr->velocity;
// Wrap the drop if it reaches an edge of the box.
wrapDrop(curr, box, currTime, windVel);
// Did the drop pass below the hit position?
if (curr->valid && curr->position.z < curr->hitPos.z)
{
// If this drop was to hit a player or vehicle double
// check to see if the object has moved out of the way.
// This keeps us from leaving phantom trails of splashes
// behind a moving player/vehicle.
if (curr->hitType & (PlayerObjectType | VehicleObjectType))
{
findDropCutoff(curr, box, windVel);
if (curr->position.z > curr->hitPos.z)
goto NO_SPLASH; // Ugly, yet simple.
}
// The drop is dead.
curr->valid = false;
// Convert the drop into a splash or let it
// wrap around and respawn in wrapDrop().
if (mSplashMS > 0)
createSplash(curr);
// So ugly... yet simple.
NO_SPLASH:;
}
// We do not do cull individual drops when we're not
// following as it is usually a tight box and all of
// the particles are in view.
if (!mFollowCam)
curr->toRender = true;
else
{
lookVec = curr->position - camPos;
curr->toRender = mDot(lookVec, camDir) > fovDot;
}
// Do we need to animate the drop?
if (curr->valid && mDropAnimateMS > 0 && curr->toRender)
{
pct = (F32)(currTime - curr->animStartTime) / mDropAnimateMS;
pct = mFmod(pct, 1);
curr->texCoordIndex = (U32)(dropCount * pct);
}
curr = curr->next;
}
//update splashes
curr = mSplashHead;
Raindrop *next;
const S32 splashCount = mDataBlock->mSplashesPerSide * mDataBlock->mSplashesPerSide;
while (curr)
{
pct = (F32)(currTime - curr->animStartTime) / mSplashMS;
if (pct >= 1.0f)
{
next = curr->nextSplashDrop;
destroySplash(curr);
curr = next;
continue;
}
if (mAnimateSplashes)
curr->texCoordIndex = (U32)(splashCount * pct);
curr = curr->nextSplashDrop;
}
PROFILE_END_NAMED(PrecipProcess);
}
//--------------------------------------------------------------------------
// Rendering
//--------------------------------------------------------------------------
void Precipitation::prepRenderImage(SceneRenderState* state)
{
PROFILE_SCOPE(Precipitation_prepRenderImage);
// We we have no drops then skip rendering
// and don't bother with the sound.
if (mMaxVBDrops == 0)
return;
// We do nothing if we're not supposed to be reflected.
if ( state->isReflectPass() && !mReflect )
return;
// This should be sufficient for most objects that don't manage zones, and
// don't need to return a specialized RenderImage...
ObjectRenderInst *ri = state->getRenderPass()->allocInst<ObjectRenderInst>();
ri->renderDelegate.bind(this, &Precipitation::renderObject);
ri->type = RenderPassManager::RIT_Foliage;
state->getRenderPass()->addInst( ri );
}
void Precipitation::renderObject(ObjectRenderInst *ri, SceneRenderState *state, BaseMatInstance* overrideMat)
{
if (overrideMat)
return;
#ifdef TORQUE_OS_XENON
return;
#endif
GameConnection* conn = GameConnection::getConnectionToServer();
if (!conn)
return; //need connection to server
ShapeBase* camObj = dynamic_cast<ShapeBase*>(conn->getCameraObject());
if (!camObj)
return; // need camera object
PROFILE_START(PrecipRender);
GFX->pushWorldMatrix();
MatrixF world = GFX->getWorldMatrix();
MatrixF proj = GFX->getProjectionMatrix();
if (!mFollowCam)
{
world.mul( getRenderTransform() );
world.scale( getScale() );
GFX->setWorldMatrix( world );
}
proj.mul(world);
//GFX2 doesn't require transpose?
//proj.transpose();
Point3F camPos = state->getCameraPosition();
VectorF camVel = camObj->getVelocity();
if (!mFollowCam)
{
getRenderWorldTransform().mulP(camPos);
getRenderWorldTransform().mulV(camVel);
}
const VectorF windVel = getWindVelocity();
const bool useBillboards = mUseTrueBillboards;
const F32 dropSize = mDropSize;
Point3F pos;
VectorF orthoDir, velocity, right, up, rightUp(0.0f, 0.0f, 0.0f), leftUp(0.0f, 0.0f, 0.0f);
F32 distance = 0;
GFXVertexPT* vertPtr = NULL;
const Point2F *tc;
// Do this here and we won't have to in the loop!
if (useBillboards)
{
MatrixF camMat = state->getCameraTransform();
camMat.inverse();
camMat.getRow(0,&right);
camMat.getRow(2,&up);
if (!mFollowCam)
{
mWorldToObj.mulV(right);
mWorldToObj.mulV(up);
}
right.normalize();
up.normalize();
right *= mDropSize;
up *= mDropSize;
rightUp = right + up;
leftUp = -right + up;
}
// We pass the sunlight as a constant to the
// shader. Once the lighting and shadow systems
// are added into TSE we can expand this to include
// the N nearest lights to the camera + the ambient.
ColorF ambient( 1, 1, 1 );
if ( mUseLighting )
{
const LightInfo *sunlight = LIGHTMGR->getSpecialLight(LightManager::slSunLightType);
ambient = sunlight->getColor();
}
if ( mGlowIntensity.red > 0 ||
mGlowIntensity.green > 0 ||
mGlowIntensity.blue > 0 )
{
ambient *= mGlowIntensity;
}
// Setup render state
if (mDefaultSB.isNull())
{
GFXStateBlockDesc desc;
desc.zWriteEnable = false;
desc.setAlphaTest(true, GFXCmpGreaterEqual, 1);
desc.setBlend(true, GFXBlendSrcAlpha, GFXBlendInvSrcAlpha);
mDefaultSB = GFX->createStateBlock(desc);
desc.samplersDefined = true;
desc.samplers[0].textureColorOp = GFXTOPModulate;
desc.samplers[0].colorArg1 = GFXTATexture;
desc.samplers[0].colorArg2 = GFXTADiffuse;
desc.samplers[0].alphaOp = GFXTOPSelectARG1;
desc.samplers[0].alphaArg1 = GFXTATexture;
desc.samplers[1].textureColorOp = GFXTOPDisable;
desc.samplers[1].alphaOp = GFXTOPDisable;
mDistantSB = GFX->createStateBlock(desc);
}
GFX->setStateBlock(mDefaultSB);
// Everything is rendered from these buffers.
GFX->setPrimitiveBuffer(mRainIB);
GFX->setVertexBuffer(mRainVB);
// Set the constants used by the shaders.
if (mDropShader)
{
Point2F fadeStartEnd( mFadeDistance, mFadeDistanceEnd );
mDropShaderConsts->setSafe(mDropShaderModelViewSC, proj);
mDropShaderConsts->setSafe(mDropShaderFadeStartEndSC, fadeStartEnd);
mDropShaderConsts->setSafe(mDropShaderCameraPosSC, camPos);
mDropShaderConsts->setSafe(mDropShaderAmbientSC, Point3F(ambient.red, ambient.green, ambient.blue));
}
if (mSplashShader)
{
Point2F fadeStartEnd( mFadeDistance, mFadeDistanceEnd );
mSplashShaderConsts->setSafe(mSplashShaderModelViewSC, proj);
mSplashShaderConsts->setSafe(mSplashShaderFadeStartEndSC, fadeStartEnd);
mSplashShaderConsts->setSafe(mSplashShaderCameraPosSC, camPos);
mSplashShaderConsts->setSafe(mSplashShaderAmbientSC, Point3F(ambient.red, ambient.green, ambient.blue));
}
// Time to render the drops...
const Raindrop *curr = mDropHead;
U32 vertCount = 0;
GFX->setTexture(0, mDropHandle);
// Use the shader or setup the pipeline
// for fixed function rendering.
if (mDropShader)
{
GFX->setShader( mDropShader );
GFX->setShaderConstBuffer( mDropShaderConsts );
}
else
{
GFX->setupGenericShaders(GFXDevice::GSTexture);
// We don't support distance fade or lighting without shaders.
GFX->setStateBlock(mDistantSB);
}
while (curr)
{
// Skip ones that are not drops (hit something and
// may have been converted into a splash) or they
// are behind the camera.
if (!curr->valid || !curr->toRender)
{
curr = curr->next;
continue;
}
pos = curr->renderPosition;
// two forms of billboards - true billboards (which we set
// above outside this loop) or axis-aligned with velocity
// (this codeblock) the axis-aligned billboards are aligned
// with the velocity of the raindrop, and tilted slightly
// towards the camera
if (!useBillboards)
{
orthoDir = camPos - pos;
distance = orthoDir.len();
// Inline the normalize so we don't
// calculate the ortho len twice.
if (distance > 0.0)
orthoDir *= 1.0f / distance;
else
orthoDir.set( 0, 0, 1 );
velocity = windVel / curr->mass;
// We do not optimize this for the "still" case
// because its not a typical scenario.
if (mRotateWithCamVel)
velocity -= camVel / (distance > 2.0f ? distance : 2.0f) * 0.3f;
velocity.z -= curr->velocity;
velocity.normalize();
right = mCross(-velocity, orthoDir);
right.normalize();
up = mCross(orthoDir, right) * 0.5 - velocity * 0.5;
up.normalize();
right *= dropSize;
up *= dropSize;
rightUp = right + up;
leftUp = -right + up;
}
// Do we need to relock the buffer?
if ( !vertPtr )
vertPtr = mRainVB.lock();
if(!vertPtr) return;
// Set the proper texture coords... (it's fun!)
tc = &mTexCoords[4*curr->texCoordIndex];
vertPtr->point = pos + leftUp;
vertPtr->texCoord = *tc;
tc++;
vertPtr++;
vertPtr->point = pos + rightUp;
vertPtr->texCoord = *tc;
tc++;
vertPtr++;
vertPtr->point = pos - leftUp;
vertPtr->texCoord = *tc;
tc++;
vertPtr++;
vertPtr->point = pos - rightUp;
vertPtr->texCoord = *tc;
tc++;
vertPtr++;
// Do we need to render to clear the buffer?
vertCount += 4;
if ( (vertCount + 4) >= mRainVB->mNumVerts ) {
mRainVB.unlock();
GFX->drawIndexedPrimitive(GFXTriangleList, 0, 0, vertCount, 0, vertCount / 2);
vertPtr = NULL;
vertCount = 0;
}
curr = curr->next;
}
// Do we have stuff left to render?
if ( vertCount > 0 ) {
mRainVB.unlock();
GFX->drawIndexedPrimitive(GFXTriangleList, 0, 0, vertCount, 0, vertCount / 2);
vertCount = 0;
vertPtr = NULL;
}
// Setup the billboard for the splashes.
MatrixF camMat = state->getCameraTransform();
camMat.inverse();
camMat.getRow(0, &right);
camMat.getRow(2, &up);
if (!mFollowCam)
{
mWorldToObj.mulV(right);
mWorldToObj.mulV(up);
}
right.normalize();
up.normalize();
right *= mSplashSize;
up *= mSplashSize;
rightUp = right + up;
leftUp = -right + up;
// Render the visible splashes.
curr = mSplashHead;
GFX->setTexture(0, mSplashHandle);
if (mSplashShader)
{
GFX->setShader( mSplashShader );
GFX->setShaderConstBuffer(mSplashShaderConsts);
}
else
GFX->setupGenericShaders(GFXDevice::GSTexture);
while (curr)
{
if (!curr->toRender)
{
curr = curr->nextSplashDrop;
continue;
}
pos = curr->hitPos;
tc = &mSplashCoords[4*curr->texCoordIndex];
// Do we need to relock the buffer?
if ( !vertPtr )
vertPtr = mRainVB.lock();
if(!vertPtr) return;
vertPtr->point = pos + leftUp;
vertPtr->texCoord = *tc;
tc++;
vertPtr++;
vertPtr->point = pos + rightUp;
vertPtr->texCoord = *tc;
tc++;
vertPtr++;
vertPtr->point = pos - leftUp;
vertPtr->texCoord = *tc;
tc++;
vertPtr++;
vertPtr->point = pos - rightUp;
vertPtr->texCoord = *tc;
tc++;
vertPtr++;
// Do we need to flush the buffer by rendering?
vertCount += 4;
if ( (vertCount + 4) >= mRainVB->mNumVerts ) {
mRainVB.unlock();
GFX->drawIndexedPrimitive(GFXTriangleList, 0, 0, vertCount, 0, vertCount / 2);
vertPtr = NULL;
vertCount = 0;
}
curr = curr->nextSplashDrop;
}
// Do we have stuff left to render?
if ( vertCount > 0 ) {
mRainVB.unlock();
GFX->drawIndexedPrimitive(GFXTriangleList, 0, 0, vertCount, 0, vertCount / 2);
}
mLastRenderFrame = ShapeBase::sLastRenderFrame;
GFX->popWorldMatrix();
PROFILE_END();
}