particleEmitter.cpp
Engine/source/T3D/fx/particleEmitter.cpp
Classes:
class
Public Defines
define
fillVert() { \ lVerts->point.x = cy * basePts->x - sy * basePts->z; \ lVerts->point.y = 0.0f; \ lVerts->point.z = sy * basePts->x + cy * basePts->z; \ camView.mulV( lVerts->point ); \ lVerts->point *= width; \ lVerts->point += part->pos; \ lVerts->color = partCol.toColorI(); } \
Public Typedefs
ParticleBlendStyle
Public Variables
Public Functions
cmpSortParticles(const void * p1, const void * p2)
ConsoleDocClass(ParticleEmitter , "@brief This object is responsible <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1a2732ab74fa0237854c2ba0f75f88a624">for</a> spawning <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">particles.\n\n</a>" "@note This class is not normally instantiated directly - <a href="/coding/file/cmdgram_8cpp/#cmdgram_8cpp_1a5bafda9519252aa2d0fd038153f77dca">to</a> place <a href="/coding/file/pointer_8h/#pointer_8h_1aeeddce917cf130d62c370b8f216026dd">a</a> simple " "particle emitting object in the scene, use <a href="/coding/file/pointer_8h/#pointer_8h_1aeeddce917cf130d62c370b8f216026dd">a</a> <a href="/coding/class/classparticleemitternode/">ParticleEmitterNode</a> <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">instead.\n\n</a>" "This class is the <a href="/coding/file/x86unixmain_8cpp/#x86unixmain_8cpp_1a217dbf8b442f20279ea00b898af96f52">main</a> interface <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1a2732ab74fa0237854c2ba0f75f88a624">for</a> creating particles - though it is " "usually only accessed from within another object like <a href="/coding/class/classparticleemitternode/">ParticleEmitterNode</a> " "or WheeledVehicle. If using this object class(via C++) directly, be aware " "that it does< <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1a91b64995742fd30063314f12340b4b5a">b</a> >not</<a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1a91b64995742fd30063314f12340b4b5a">b</a> > track changes in <a href="/coding/file/pointer_8h/#pointer_8h_1adb82dfe18535e9a30aa97d275f82bd55">source</a> axis or velocity over the " "course of <a href="/coding/file/pointer_8h/#pointer_8h_1aeeddce917cf130d62c370b8f216026dd">a</a> single update, so emitParticles should be called at <a href="/coding/file/pointer_8h/#pointer_8h_1aeeddce917cf130d62c370b8f216026dd">a</a> fairly " "fine grain. The emitter will potentially track the last particle <a href="/coding/file/cmdgram_8cpp/#cmdgram_8cpp_1a5bafda9519252aa2d0fd038153f77dca">to</a> be " "created into the next call <a href="/coding/file/cmdgram_8cpp/#cmdgram_8cpp_1a5bafda9519252aa2d0fd038153f77dca">to</a> this function in order <a href="/coding/file/cmdgram_8cpp/#cmdgram_8cpp_1a5bafda9519252aa2d0fd038153f77dca">to</a> create <a href="/coding/file/pointer_8h/#pointer_8h_1aeeddce917cf130d62c370b8f216026dd">a</a> uniformly " "random time distribution of the <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">particles.\n\n</a>" "If the object <a href="/coding/file/cmdgram_8cpp/#cmdgram_8cpp_1a5bafda9519252aa2d0fd038153f77dca">to</a> which the emitter is attached is in motion, it should try " "<a href="/coding/file/cmdgram_8cpp/#cmdgram_8cpp_1a5bafda9519252aa2d0fd038153f77dca">to</a> ensure that <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1a2732ab74fa0237854c2ba0f75f88a624">for</a> call(<a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">n</a>+1) <a href="/coding/file/cmdgram_8cpp/#cmdgram_8cpp_1a5bafda9519252aa2d0fd038153f77dca">to</a> this function, start is equal <a href="/coding/file/cmdgram_8cpp/#cmdgram_8cpp_1a5bafda9519252aa2d0fd038153f77dca">to</a> the end " "from call(<a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">n</a>). This will ensure <a href="/coding/file/pointer_8h/#pointer_8h_1aeeddce917cf130d62c370b8f216026dd">a</a> uniform spatial <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">distribution.\n\n</a>" " @ingroup <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">FX\n</a>" " @see <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">ParticleEmitterData\n</a>" " @see <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">ParticleEmitterNode\n</a>" )
ConsoleDocClass(ParticleEmitterData , "@brief Defines particle emission properties such as ejection angle, period " "and velocity <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1a2732ab74fa0237854c2ba0f75f88a624">for</a> <a href="/coding/file/pointer_8h/#pointer_8h_1aeeddce917cf130d62c370b8f216026dd">a</a> <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">ParticleEmitter.\n\n</a>" " @<a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">tsexample\n</a>" "datablock <a href="/coding/class/classparticleemitterdata/">ParticleEmitterData</a>(GrenadeExpDustEmitter)\<a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">n</a>" "{\<a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">n</a>" " ejectionPeriodMS=1;\<a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">n</a>" " periodVarianceMS=0;\<a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">n</a>" " ejectionVelocity=15;\<a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">n</a>" " velocityVariance=0.0;\<a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">n</a>" " ejectionOffset=0.0;\<a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">n</a>" " thetaMin=85;\<a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">n</a>" " thetaMax=85;\<a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">n</a>" " thetaVariance=0;\<a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">n</a>" " phiReferenceVel=0;\<a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">n</a>" " phiVariance=360;\<a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">n</a>" " overrideAdvance=false;\<a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">n</a>" " lifetimeMS=200;\<a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">n</a>" " particles=\"GrenadeExpDust\";\n" "};\<a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">n</a>" "@<a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">endtsexample\n\n</a>" "@ingroup <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">FX\n</a>" "@see <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">ParticleEmitter\n</a>" "@see <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">ParticleData\n</a>" "@see <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">ParticleEmitterNode\n</a>" )
DefineEngineMethod(ParticleEmitterData , reload , void , () )
ejectionFValidator(0. f, 655. 35f)
ImplementEnumType(ParticleBlendStyle , "The type of visual blending style <a href="/coding/file/cmdgram_8cpp/#cmdgram_8cpp_1a5bafda9519252aa2d0fd038153f77dca">to</a> apply <a href="/coding/file/cmdgram_8cpp/#cmdgram_8cpp_1a5bafda9519252aa2d0fd038153f77dca">to</a> the <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">particles.\n</a>" "@ingroup <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">FX\n\n</a>" )
phiFValidator(0. f, 360. f)
thetaFValidator(0. f, 180. f)
velVarianceFValidator(0. f, 163. 83f)
Detailed Description
Public Defines
fillVert() { \ lVerts->point.x = cy * basePts->x - sy * basePts->z; \ lVerts->point.y = 0.0f; \ lVerts->point.z = sy * basePts->x + cy * basePts->z; \ camView.mulV( lVerts->point ); \ lVerts->point *= width; \ lVerts->point += part->pos; \ lVerts->color = partCol.toColorI(); } \
Public Typedefs
typedef ParticleRenderInst::BlendStyle ParticleBlendStyle
Public Variables
IRangeValidator ejectPeriodIValidator (1, 2047)
EndImplementEnumType
IRangeValidator periodVarianceIValidator (0, 2047)
const F32 sgDefaultEjectionOffset
const F32 sgDefaultPhiReferenceVel
const F32 sgDefaultPhiVariance
Public Functions
cmpSortParticles(const void * p1, const void * p2)
ConsoleDocClass(ParticleEmitter , "@brief This object is responsible <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1a2732ab74fa0237854c2ba0f75f88a624">for</a> spawning <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">particles.\n\n</a>" "@note This class is not normally instantiated directly - <a href="/coding/file/cmdgram_8cpp/#cmdgram_8cpp_1a5bafda9519252aa2d0fd038153f77dca">to</a> place <a href="/coding/file/pointer_8h/#pointer_8h_1aeeddce917cf130d62c370b8f216026dd">a</a> simple " "particle emitting object in the scene, use <a href="/coding/file/pointer_8h/#pointer_8h_1aeeddce917cf130d62c370b8f216026dd">a</a> <a href="/coding/class/classparticleemitternode/">ParticleEmitterNode</a> <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">instead.\n\n</a>" "This class is the <a href="/coding/file/x86unixmain_8cpp/#x86unixmain_8cpp_1a217dbf8b442f20279ea00b898af96f52">main</a> interface <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1a2732ab74fa0237854c2ba0f75f88a624">for</a> creating particles - though it is " "usually only accessed from within another object like <a href="/coding/class/classparticleemitternode/">ParticleEmitterNode</a> " "or WheeledVehicle. If using this object class(via C++) directly, be aware " "that it does< <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1a91b64995742fd30063314f12340b4b5a">b</a> >not</<a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1a91b64995742fd30063314f12340b4b5a">b</a> > track changes in <a href="/coding/file/pointer_8h/#pointer_8h_1adb82dfe18535e9a30aa97d275f82bd55">source</a> axis or velocity over the " "course of <a href="/coding/file/pointer_8h/#pointer_8h_1aeeddce917cf130d62c370b8f216026dd">a</a> single update, so emitParticles should be called at <a href="/coding/file/pointer_8h/#pointer_8h_1aeeddce917cf130d62c370b8f216026dd">a</a> fairly " "fine grain. The emitter will potentially track the last particle <a href="/coding/file/cmdgram_8cpp/#cmdgram_8cpp_1a5bafda9519252aa2d0fd038153f77dca">to</a> be " "created into the next call <a href="/coding/file/cmdgram_8cpp/#cmdgram_8cpp_1a5bafda9519252aa2d0fd038153f77dca">to</a> this function in order <a href="/coding/file/cmdgram_8cpp/#cmdgram_8cpp_1a5bafda9519252aa2d0fd038153f77dca">to</a> create <a href="/coding/file/pointer_8h/#pointer_8h_1aeeddce917cf130d62c370b8f216026dd">a</a> uniformly " "random time distribution of the <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">particles.\n\n</a>" "If the object <a href="/coding/file/cmdgram_8cpp/#cmdgram_8cpp_1a5bafda9519252aa2d0fd038153f77dca">to</a> which the emitter is attached is in motion, it should try " "<a href="/coding/file/cmdgram_8cpp/#cmdgram_8cpp_1a5bafda9519252aa2d0fd038153f77dca">to</a> ensure that <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1a2732ab74fa0237854c2ba0f75f88a624">for</a> call(<a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">n</a>+1) <a href="/coding/file/cmdgram_8cpp/#cmdgram_8cpp_1a5bafda9519252aa2d0fd038153f77dca">to</a> this function, start is equal <a href="/coding/file/cmdgram_8cpp/#cmdgram_8cpp_1a5bafda9519252aa2d0fd038153f77dca">to</a> the end " "from call(<a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">n</a>). This will ensure <a href="/coding/file/pointer_8h/#pointer_8h_1aeeddce917cf130d62c370b8f216026dd">a</a> uniform spatial <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">distribution.\n\n</a>" " @ingroup <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">FX\n</a>" " @see <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">ParticleEmitterData\n</a>" " @see <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">ParticleEmitterNode\n</a>" )
ConsoleDocClass(ParticleEmitterData , "@brief Defines particle emission properties such as ejection angle, period " "and velocity <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1a2732ab74fa0237854c2ba0f75f88a624">for</a> <a href="/coding/file/pointer_8h/#pointer_8h_1aeeddce917cf130d62c370b8f216026dd">a</a> <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">ParticleEmitter.\n\n</a>" " @<a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">tsexample\n</a>" "datablock <a href="/coding/class/classparticleemitterdata/">ParticleEmitterData</a>(GrenadeExpDustEmitter)\<a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">n</a>" "{\<a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">n</a>" " ejectionPeriodMS=1;\<a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">n</a>" " periodVarianceMS=0;\<a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">n</a>" " ejectionVelocity=15;\<a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">n</a>" " velocityVariance=0.0;\<a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">n</a>" " ejectionOffset=0.0;\<a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">n</a>" " thetaMin=85;\<a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">n</a>" " thetaMax=85;\<a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">n</a>" " thetaVariance=0;\<a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">n</a>" " phiReferenceVel=0;\<a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">n</a>" " phiVariance=360;\<a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">n</a>" " overrideAdvance=false;\<a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">n</a>" " lifetimeMS=200;\<a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">n</a>" " particles=\"GrenadeExpDust\";\n" "};\<a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">n</a>" "@<a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">endtsexample\n\n</a>" "@ingroup <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">FX\n</a>" "@see <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">ParticleEmitter\n</a>" "@see <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">ParticleData\n</a>" "@see <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">ParticleEmitterNode\n</a>" )
DefineEngineMethod(ParticleEmitterData , reload , void , () )
DefineEnumType(ParticleBlendStyle )
ejectionFValidator(0. f, 655. 35f)
IMPLEMENT_CO_DATABLOCK_V1(ParticleEmitterData )
IMPLEMENT_CONOBJECT(ParticleEmitter )
ImplementEnumType(ParticleBlendStyle , "The type of visual blending style <a href="/coding/file/cmdgram_8cpp/#cmdgram_8cpp_1a5bafda9519252aa2d0fd038153f77dca">to</a> apply <a href="/coding/file/cmdgram_8cpp/#cmdgram_8cpp_1a5bafda9519252aa2d0fd038153f77dca">to</a> the <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">particles.\n</a>" "@ingroup <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">FX\n\n</a>" )
phiFValidator(0. f, 360. f)
thetaFValidator(0. f, 180. f)
velVarianceFValidator(0. f, 163. 83f)
1 2//----------------------------------------------------------------------------- 3// Copyright (c) 2012 GarageGames, LLC 4// 5// Permission is hereby granted, free of charge, to any person obtaining a copy 6// of this software and associated documentation files (the "Software"), to 7// deal in the Software without restriction, including without limitation the 8// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or 9// sell copies of the Software, and to permit persons to whom the Software is 10// furnished to do so, subject to the following conditions: 11// 12// The above copyright notice and this permission notice shall be included in 13// all copies or substantial portions of the Software. 14// 15// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 20// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS 21// IN THE SOFTWARE. 22//----------------------------------------------------------------------------- 23 24//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// 25// Arcane-FX for MIT Licensed Open Source version of Torque 3D from GarageGames 26// Copyright (C) 2015 Faust Logic, Inc. 27//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// 28 29#include "platform/platform.h" 30#include "T3D/fx/particleEmitter.h" 31 32#include "scene/sceneManager.h" 33#include "scene/sceneRenderState.h" 34#include "console/consoleTypes.h" 35#include "console/typeValidators.h" 36#include "core/stream/bitStream.h" 37#include "core/strings/stringUnit.h" 38#include "math/mRandom.h" 39#include "gfx/gfxDevice.h" 40#include "gfx/primBuilder.h" 41#include "gfx/gfxStringEnumTranslate.h" 42#include "renderInstance/renderPassManager.h" 43#include "T3D/gameBase/gameProcess.h" 44#include "lighting/lightInfo.h" 45#include "console/engineAPI.h" 46 47#if defined(AFX_CAP_PARTICLE_POOLS) 48#include "afx/util/afxParticlePool.h" 49#endif 50 51Point3F ParticleEmitter::mWindVelocity( 0.0, 0.0, 0.0 ); 52const F32 ParticleEmitter::AgedSpinToRadians = (1.0f/1000.0f) * (1.0f/360.0f) * M_PI_F * 2.0f; 53 54IMPLEMENT_CO_DATABLOCK_V1(ParticleEmitterData); 55IMPLEMENT_CONOBJECT(ParticleEmitter); 56 57ConsoleDocClass( ParticleEmitter, 58 "@brief This object is responsible for spawning particles.\n\n" 59 60 "@note This class is not normally instantiated directly - to place a simple " 61 "particle emitting object in the scene, use a ParticleEmitterNode instead.\n\n" 62 63 "This class is the main interface for creating particles - though it is " 64 "usually only accessed from within another object like ParticleEmitterNode " 65 "or WheeledVehicle. If using this object class (via C++) directly, be aware " 66 "that it does <b>not</b> track changes in source axis or velocity over the " 67 "course of a single update, so emitParticles should be called at a fairly " 68 "fine grain. The emitter will potentially track the last particle to be " 69 "created into the next call to this function in order to create a uniformly " 70 "random time distribution of the particles.\n\n" 71 72 "If the object to which the emitter is attached is in motion, it should try " 73 "to ensure that for call (n+1) to this function, start is equal to the end " 74 "from call (n). This will ensure a uniform spatial distribution.\n\n" 75 76 "@ingroup FX\n" 77 "@see ParticleEmitterData\n" 78 "@see ParticleEmitterNode\n" 79); 80 81ConsoleDocClass( ParticleEmitterData, 82 "@brief Defines particle emission properties such as ejection angle, period " 83 "and velocity for a ParticleEmitter.\n\n" 84 85 "@tsexample\n" 86 "datablock ParticleEmitterData( GrenadeExpDustEmitter )\n" 87 "{\n" 88 " ejectionPeriodMS = 1;\n" 89 " periodVarianceMS = 0;\n" 90 " ejectionVelocity = 15;\n" 91 " velocityVariance = 0.0;\n" 92 " ejectionOffset = 0.0;\n" 93 " thetaMin = 85;\n" 94 " thetaMax = 85;\n" 95 " thetaVariance = 0;\n" 96 " phiReferenceVel = 0;\n" 97 " phiVariance = 360;\n" 98 " overrideAdvance = false;\n" 99 " lifetimeMS = 200;\n" 100 " particles = \"GrenadeExpDust\";\n" 101 "};\n" 102 "@endtsexample\n\n" 103 104 "@ingroup FX\n" 105 "@see ParticleEmitter\n" 106 "@see ParticleData\n" 107 "@see ParticleEmitterNode\n" 108); 109 110static const F32 sgDefaultEjectionOffset = 0.f; 111static const F32 sgDefaultPhiReferenceVel = 0.f; 112static const F32 sgDefaultPhiVariance = 360.f; 113 114//----------------------------------------------------------------------------- 115// ParticleEmitterData 116//----------------------------------------------------------------------------- 117ParticleEmitterData::ParticleEmitterData() 118{ 119 VECTOR_SET_ASSOCIATION(particleDataBlocks); 120 VECTOR_SET_ASSOCIATION(dataBlockIds); 121 122 ejectionPeriodMS = 100; // 10 Particles Per second 123 periodVarianceMS = 0; // exactly 124 125 ejectionVelocity = 2.0f; // From 1.0 - 3.0 meters per sec 126 velocityVariance = 1.0f; 127 ejectionOffset = sgDefaultEjectionOffset; // ejection from the emitter point 128 ejectionOffsetVariance = 0.0f; 129 130 thetaMin = 0.0f; // All heights 131 thetaMax = 90.0f; 132 thetaVariance = 0.0f; 133 134 phiReferenceVel = sgDefaultPhiReferenceVel; // All directions 135 phiVariance = sgDefaultPhiVariance; 136 137 softnessDistance = 1.0f; 138 ambientFactor = 0.0f; 139 140 lifetimeMS = 0; 141 lifetimeVarianceMS = 0; 142 143 overrideAdvance = true; 144 orientParticles = false; 145 orientOnVelocity = true; 146 ribbonParticles = false; 147 useEmitterSizes = false; 148 useEmitterColors = false; 149 particleString = NULL; 150 partListInitSize = 0; 151 152 // These members added for support of user defined blend factors 153 // and optional particle sorting. 154 blendStyle = ParticleRenderInst::BlendUndefined; 155 sortParticles = false; 156 renderReflection = true; 157 glow = false; 158 reverseOrder = false; 159 textureName = 0; 160 textureHandle = 0; 161 highResOnly = true; 162 163 alignParticles = false; 164 alignDirection = Point3F(0.0f, 1.0f, 0.0f); 165 ejectionInvert = false; 166 fade_color = false; 167 fade_alpha = false; 168 fade_size = false; 169 parts_per_eject = 1; 170 use_emitter_xfm = false; 171 172#if defined(AFX_CAP_PARTICLE_POOLS) 173 pool_datablock = 0; 174 pool_index = 0; 175 pool_depth_fade = false; 176 pool_radial_fade = false; 177 do_pool_id_convert = false; 178#endif 179} 180 181 182 183// Enum tables used for fields blendStyle, srcBlendFactor, dstBlendFactor. 184// Note that the enums for srcBlendFactor and dstBlendFactor are consistent 185// with the blending enums used in Torque Game Builder. 186 187typedef ParticleRenderInst::BlendStyle ParticleBlendStyle; 188DefineEnumType( ParticleBlendStyle ); 189 190ImplementEnumType( ParticleBlendStyle, 191 "The type of visual blending style to apply to the particles.\n" 192 "@ingroup FX\n\n") 193 { ParticleRenderInst::BlendNormal, "NORMAL", "No blending style.\n" }, 194 { ParticleRenderInst::BlendAdditive, "ADDITIVE", "Adds the color of the pixel to the frame buffer with full alpha for each pixel.\n" }, 195 { ParticleRenderInst::BlendSubtractive, "SUBTRACTIVE", "Subtractive Blending. Reverses the color model, causing dark colors to have a stronger visual effect.\n" }, 196 { ParticleRenderInst::BlendPremultAlpha, "PREMULTALPHA", "Color blends with the colors of the imagemap rather than the alpha.\n" }, 197EndImplementEnumType; 198 199IRangeValidator ejectPeriodIValidator(1, 2047); 200IRangeValidator periodVarianceIValidator(0, 2047); 201FRangeValidator ejectionFValidator(0.f, 655.35f); 202FRangeValidator velVarianceFValidator(0.f, 163.83f); 203FRangeValidator thetaFValidator(0.f, 180.f); 204FRangeValidator phiFValidator(0.f, 360.f); 205 206//----------------------------------------------------------------------------- 207// initPersistFields 208//----------------------------------------------------------------------------- 209void ParticleEmitterData::initPersistFields() 210{ 211 addGroup( "ParticleEmitterData" ); 212 213 addFieldV("ejectionPeriodMS", TYPEID< S32 >(), Offset(ejectionPeriodMS, ParticleEmitterData), &ejectPeriodIValidator, 214 "Time (in milliseconds) between each particle ejection." ); 215 216 addFieldV("periodVarianceMS", TYPEID< S32 >(), Offset(periodVarianceMS, ParticleEmitterData), &periodVarianceIValidator, 217 "Variance in ejection period, from 1 - ejectionPeriodMS." ); 218 219 addFieldV( "ejectionVelocity", TYPEID< F32 >(), Offset(ejectionVelocity, ParticleEmitterData), &ejectionFValidator, 220 "Particle ejection velocity." ); 221 222 addFieldV( "velocityVariance", TYPEID< F32 >(), Offset(velocityVariance, ParticleEmitterData), &velVarianceFValidator, 223 "Variance for ejection velocity, from 0 - ejectionVelocity." ); 224 225 addFieldV( "ejectionOffset", TYPEID< F32 >(), Offset(ejectionOffset, ParticleEmitterData), &ejectionFValidator, 226 "Distance along ejection Z axis from which to eject particles." ); 227 228 addFieldV( "ejectionOffsetVariance", TYPEID< F32 >(), Offset(ejectionOffsetVariance, ParticleEmitterData), &ejectionFValidator, 229 "Distance Padding along ejection Z axis from which to eject particles." ); 230 231 addFieldV( "thetaMin", TYPEID< F32 >(), Offset(thetaMin, ParticleEmitterData), &thetaFValidator, 232 "Minimum angle, from the horizontal plane, to eject from." ); 233 234 addFieldV( "thetaMax", TYPEID< F32 >(), Offset(thetaMax, ParticleEmitterData), &thetaFValidator, 235 "Maximum angle, from the horizontal plane, to eject particles from." ); 236 237 addFieldV( "thetaVariance", TYPEID< F32 >(), Offset(thetaVariance, ParticleEmitterData), &thetaFValidator, 238 "Angle variance from the previous particle, from 0 - 180." ); 239 240 addFieldV( "phiReferenceVel", TYPEID< F32 >(), Offset(phiReferenceVel, ParticleEmitterData), &phiFValidator, 241 "Reference angle, from the vertical plane, to eject particles from." ); 242 243 addFieldV( "phiVariance", TYPEID< F32 >(), Offset(phiVariance, ParticleEmitterData), &phiFValidator, 244 "Variance from the reference angle, from 0 - 360." ); 245 246 addField( "softnessDistance", TYPEID< F32 >(), Offset(softnessDistance, ParticleEmitterData), 247 "For soft particles, the distance (in meters) where particles will be " 248 "faded based on the difference in depth between the particle and the " 249 "scene geometry." ); 250 251 addField( "ambientFactor", TYPEID< F32 >(), Offset(ambientFactor, ParticleEmitterData), 252 "Used to generate the final particle color by controlling interpolation " 253 "between the particle color and the particle color multiplied by the " 254 "ambient light color." ); 255 256 addField( "overrideAdvance", TYPEID< bool >(), Offset(overrideAdvance, ParticleEmitterData), 257 "If false, particles emitted in the same frame have their positions " 258 "adjusted. If true, adjustment is skipped and particles will clump " 259 "together." ); 260 261 addField( "orientParticles", TYPEID< bool >(), Offset(orientParticles, ParticleEmitterData), 262 "If true, Particles will always face the camera." ); 263 264 addField( "orientOnVelocity", TYPEID< bool >(), Offset(orientOnVelocity, ParticleEmitterData), 265 "If true, particles will be oriented to face in the direction they are moving." ); 266 267 addField( "ribbonParticles", TYPEID< bool >(), Offset(ribbonParticles, ParticleEmitterData), 268 "If true, particles are rendered as a continous ribbon." ); 269 270 addField( "particles", TYPEID< StringTableEntry >(), Offset(particleString, ParticleEmitterData), 271 "@brief List of space or TAB delimited ParticleData datablock names.\n\n" 272 "A random one of these datablocks is selected each time a particle is " 273 "emitted." ); 274 275 addField( "lifetimeMS", TYPEID< S32 >(), Offset(lifetimeMS, ParticleEmitterData), 276 "Lifetime of emitted particles (in milliseconds)." ); 277 278 addField("lifetimeVarianceMS", TYPEID< S32 >(), Offset(lifetimeVarianceMS, ParticleEmitterData), 279 "Variance in particle lifetime from 0 - lifetimeMS." ); 280 281 addField( "useEmitterSizes", TYPEID< bool >(), Offset(useEmitterSizes, ParticleEmitterData), 282 "@brief If true, use emitter specified sizes instead of datablock sizes.\n" 283 "Useful for Debris particle emitters that control the particle size." ); 284 285 addField( "useEmitterColors", TYPEID< bool >(), Offset(useEmitterColors, ParticleEmitterData), 286 "@brief If true, use emitter specified colors instead of datablock colors.\n\n" 287 "Useful for ShapeBase dust and WheeledVehicle wheel particle emitters that use " 288 "the current material to control particle color." ); 289 290 /// These fields added for support of user defined blend factors and optional particle sorting. 291 //@{ 292 addField( "blendStyle", TYPEID< ParticleRenderInst::BlendStyle >(), Offset(blendStyle, ParticleEmitterData), 293 "String value that controls how emitted particles blend with the scene." ); 294 295 addField( "sortParticles", TYPEID< bool >(), Offset(sortParticles, ParticleEmitterData), 296 "If true, particles are sorted furthest to nearest."); 297 298 addField( "reverseOrder", TYPEID< bool >(), Offset(reverseOrder, ParticleEmitterData), 299 "@brief If true, reverses the normal draw order of particles.\n\n" 300 "Particles are normally drawn from newest to oldest, or in Z order " 301 "(furthest first) if sortParticles is true. Setting this field to " 302 "true will reverse that order: oldest first, or nearest first if " 303 "sortParticles is true." ); 304 305 addField( "textureName", TYPEID< StringTableEntry >(), Offset(textureName, ParticleEmitterData), 306 "Optional texture to override ParticleData::textureName." ); 307 308 addField( "alignParticles", TYPEID< bool >(), Offset(alignParticles, ParticleEmitterData), 309 "If true, particles always face along the axis defined by alignDirection." ); 310 311 addProtectedField( "alignDirection", TYPEID< Point3F>(), Offset(alignDirection, ParticleEmitterData), &ParticleEmitterData::_setAlignDirection, &defaultProtectedGetFn, 312 "The direction aligned particles should face, only valid if alignParticles is true." ); 313 314 addField( "highResOnly", TYPEID< bool >(), Offset(highResOnly, ParticleEmitterData), 315 "This particle system should not use the mixed-resolution renderer. " 316 "If your particle system has large amounts of overdraw, consider " 317 "disabling this option." ); 318 319 addField( "renderReflection", TYPEID< bool >(), Offset(renderReflection, ParticleEmitterData), 320 "Controls whether particles are rendered onto reflective surfaces like water." ); 321 322 addField("glow", TYPEID< bool >(), Offset(glow, ParticleEmitterData), 323 "If true, the particles are rendered to the glow buffer as well."); 324 325 //@} 326 327 endGroup( "ParticleEmitterData" ); 328 329 addGroup("AFX"); 330 addField("ejectionInvert", TypeBool, Offset(ejectionInvert, ParticleEmitterData)); 331 addField("fadeColor", TypeBool, Offset(fade_color, ParticleEmitterData)); 332 addField("fadeAlpha", TypeBool, Offset(fade_alpha, ParticleEmitterData)); 333 addField("fadeSize", TypeBool, Offset(fade_size, ParticleEmitterData)); 334 // useEmitterTransform currently does not work in TGEA or T3D 335 addField("useEmitterTransform", TypeBool, Offset(use_emitter_xfm, ParticleEmitterData)); 336 endGroup("AFX"); 337 338#if defined(AFX_CAP_PARTICLE_POOLS) 339 addGroup("AFX Pooled Particles"); 340 addField("poolData", TYPEID<afxParticlePoolData>(), Offset(pool_datablock, ParticleEmitterData)); 341 addField("poolIndex", TypeS32, Offset(pool_index, ParticleEmitterData)); 342 addField("poolDepthFade", TypeBool, Offset(pool_depth_fade, ParticleEmitterData)); 343 addField("poolRadialFade", TypeBool, Offset(pool_radial_fade, ParticleEmitterData)); 344 endGroup("AFX Pooled Particles"); 345#endif 346 // disallow some field substitutions 347 disableFieldSubstitutions("particles"); 348 onlyKeepClearSubstitutions("poolData"); // subs resolving to "~~", or "~0" are OK 349 Parent::initPersistFields(); 350} 351 352bool ParticleEmitterData::_setAlignDirection( void *object, const char *index, const char *data ) 353{ 354 ParticleEmitterData *p = static_cast<ParticleEmitterData*>( object ); 355 356 Con::setData( TypePoint3F, &p->alignDirection, 0, 1, &data ); 357 p->alignDirection.normalizeSafe(); 358 359 // we already set the field 360 return false; 361} 362 363//----------------------------------------------------------------------------- 364// packData 365//----------------------------------------------------------------------------- 366void ParticleEmitterData::packData(BitStream* stream) 367{ 368 Parent::packData(stream); 369 370 stream->writeInt(ejectionPeriodMS, 11); // must match limit on valid range in ParticleEmitterData::initPersistFields 371 stream->writeInt(periodVarianceMS, 11); 372 stream->writeInt((S32)(ejectionVelocity * 100), 16); 373 stream->writeInt((S32)(velocityVariance * 100), 14); 374 if( stream->writeFlag( ejectionOffset != sgDefaultEjectionOffset ) ) 375 stream->writeInt((S32)(ejectionOffset * 100), 16); 376 if( stream->writeFlag( ejectionOffsetVariance != 0.0f ) ) 377 stream->writeInt((S32)(ejectionOffsetVariance * 100), 16); 378 stream->writeRangedU32((U32)thetaMin, 0, 180); 379 stream->writeRangedU32((U32)thetaMax, 0, 180); 380 stream->writeRangedU32((U32)thetaVariance, 0, 180); 381 if( stream->writeFlag( phiReferenceVel != sgDefaultPhiReferenceVel ) ) 382 stream->writeRangedU32((U32)phiReferenceVel, 0, 360); 383 if( stream->writeFlag( phiVariance != sgDefaultPhiVariance ) ) 384 stream->writeRangedU32((U32)phiVariance, 0, 360); 385 386 stream->write( softnessDistance ); 387 stream->write( ambientFactor ); 388 389 stream->writeFlag(overrideAdvance); 390 stream->writeFlag(orientParticles); 391 stream->writeFlag(orientOnVelocity); 392 stream->writeFlag(ribbonParticles); 393 stream->write( lifetimeMS ); 394 stream->write( lifetimeVarianceMS ); 395 stream->writeFlag(useEmitterSizes); 396 stream->writeFlag(useEmitterColors); 397 398 stream->write(dataBlockIds.size()); 399 for (U32 i = 0; i < dataBlockIds.size(); i++) 400 stream->write(dataBlockIds[i]); 401 stream->writeFlag(sortParticles); 402 stream->writeFlag(reverseOrder); 403 if (stream->writeFlag(textureName != 0)) 404 stream->writeString(textureName); 405 406 if (stream->writeFlag(alignParticles)) 407 { 408 stream->write(alignDirection.x); 409 stream->write(alignDirection.y); 410 stream->write(alignDirection.z); 411 } 412 stream->writeFlag(highResOnly); 413 stream->writeFlag(renderReflection); 414 stream->writeFlag(glow); 415 stream->writeInt( blendStyle, 4 ); 416 417 stream->writeFlag(ejectionInvert); 418 stream->writeFlag(fade_color); 419 stream->writeFlag(fade_alpha); 420 stream->writeFlag(fade_size); 421 stream->writeFlag(use_emitter_xfm); 422 423#if defined(AFX_CAP_PARTICLE_POOLS) 424 if (stream->writeFlag(pool_datablock)) 425 { 426 stream->writeRangedU32(mPacked ? SimObjectId((uintptr_t)pool_datablock) : pool_datablock->getId(), DataBlockObjectIdFirst, DataBlockObjectIdLast); 427 stream->write(pool_index); 428 stream->writeFlag(pool_depth_fade); 429 stream->writeFlag(pool_radial_fade); 430 } 431#endif 432} 433 434//----------------------------------------------------------------------------- 435// unpackData 436//----------------------------------------------------------------------------- 437void ParticleEmitterData::unpackData(BitStream* stream) 438{ 439 Parent::unpackData(stream); 440 441 ejectionPeriodMS = stream->readInt(11); 442 periodVarianceMS = stream->readInt(11); 443 ejectionVelocity = stream->readInt(16) / 100.0f; 444 velocityVariance = stream->readInt(14) / 100.0f; 445 if( stream->readFlag() ) 446 ejectionOffset = stream->readInt(16) / 100.0f; 447 else 448 ejectionOffset = sgDefaultEjectionOffset; 449 if( stream->readFlag() ) 450 ejectionOffsetVariance = stream->readInt(16) / 100.0f; 451 else 452 ejectionOffsetVariance = 0.0f; 453 thetaMin = (F32)stream->readRangedU32(0, 180); 454 thetaMax = (F32)stream->readRangedU32(0, 180); 455 thetaVariance = (F32)stream->readRangedU32(0, 180); 456 if( stream->readFlag() ) 457 phiReferenceVel = (F32)stream->readRangedU32(0, 360); 458 else 459 phiReferenceVel = sgDefaultPhiReferenceVel; 460 461 if( stream->readFlag() ) 462 phiVariance = (F32)stream->readRangedU32(0, 360); 463 else 464 phiVariance = sgDefaultPhiVariance; 465 466 stream->read( &softnessDistance ); 467 stream->read( &ambientFactor ); 468 469 overrideAdvance = stream->readFlag(); 470 orientParticles = stream->readFlag(); 471 orientOnVelocity = stream->readFlag(); 472 ribbonParticles = stream->readFlag(); 473 stream->read( &lifetimeMS ); 474 stream->read( &lifetimeVarianceMS ); 475 useEmitterSizes = stream->readFlag(); 476 useEmitterColors = stream->readFlag(); 477 478 U32 size; stream->read(&size); 479 dataBlockIds.setSize(size); 480 for (U32 i = 0; i < dataBlockIds.size(); i++) 481 stream->read(&dataBlockIds[i]); 482 sortParticles = stream->readFlag(); 483 reverseOrder = stream->readFlag(); 484 textureName = (stream->readFlag()) ? stream->readSTString() : 0; 485 486 alignParticles = stream->readFlag(); 487 if (alignParticles) 488 { 489 stream->read(&alignDirection.x); 490 stream->read(&alignDirection.y); 491 stream->read(&alignDirection.z); 492 } 493 highResOnly = stream->readFlag(); 494 renderReflection = stream->readFlag(); 495 glow = stream->readFlag(); 496 blendStyle = stream->readInt( 4 ); 497 ejectionInvert = stream->readFlag(); 498 fade_color = stream->readFlag(); 499 fade_alpha = stream->readFlag(); 500 fade_size = stream->readFlag(); 501 use_emitter_xfm = stream->readFlag(); 502 503#if defined(AFX_CAP_PARTICLE_POOLS) 504 if (stream->readFlag()) 505 { 506 pool_datablock = (afxParticlePoolData*)(uintptr_t)stream->readRangedU32(DataBlockObjectIdFirst, DataBlockObjectIdLast); 507 stream->read(&pool_index); 508 pool_depth_fade = stream->readFlag(); 509 pool_radial_fade = stream->readFlag(); 510 do_pool_id_convert = true; 511 } 512#endif 513} 514 515//----------------------------------------------------------------------------- 516// onAdd 517//----------------------------------------------------------------------------- 518bool ParticleEmitterData::onAdd() 519{ 520 if( Parent::onAdd() == false ) 521 return false; 522 523// if (overrideAdvance == true) { 524// Con::errorf(ConsoleLogEntry::General, "ParticleEmitterData: Not going to work. Fix it!"); 525// return false; 526// } 527 528 // Validate the parameters... 529 // 530 if( ejectionPeriodMS < 1 ) 531 { 532 Con::warnf(ConsoleLogEntry::General, "ParticleEmitterData(%s) period < 1 ms", getName()); 533 ejectionPeriodMS = 1; 534 } 535 if( periodVarianceMS >= ejectionPeriodMS ) 536 { 537 Con::warnf(ConsoleLogEntry::General, "ParticleEmitterData(%s) periodVariance >= period", getName()); 538 periodVarianceMS = ejectionPeriodMS - 1; 539 } 540 if( ejectionVelocity < 0.0f ) 541 { 542 Con::warnf(ConsoleLogEntry::General, "ParticleEmitterData(%s) ejectionVelocity < 0.0f", getName()); 543 ejectionVelocity = 0.0f; 544 } 545 if( velocityVariance < 0.0f ) 546 { 547 Con::warnf(ConsoleLogEntry::General, "ParticleEmitterData(%s) velocityVariance < 0.0f", getName()); 548 velocityVariance = 0.0f; 549 } 550 if( velocityVariance > ejectionVelocity ) 551 { 552 Con::warnf(ConsoleLogEntry::General, "ParticleEmitterData(%s) velocityVariance > ejectionVelocity", getName()); 553 velocityVariance = ejectionVelocity; 554 } 555 if( ejectionOffset < 0.0f ) 556 { 557 Con::warnf(ConsoleLogEntry::General, "ParticleEmitterData(%s) ejectionOffset < 0", getName()); 558 ejectionOffset = 0.0f; 559 } 560 if( ejectionOffsetVariance < 0.0f ) 561 { 562 Con::warnf(ConsoleLogEntry::General, "ParticleEmitterData(%s) ejectionOffset < 0", getName()); 563 ejectionOffsetVariance = 0.0f; 564 } 565 if( thetaMin < 0.0f ) 566 { 567 Con::warnf(ConsoleLogEntry::General, "ParticleEmitterData(%s) thetaMin < 0.0", getName()); 568 thetaMin = 0.0f; 569 } 570 if( thetaMax > 180.0f ) 571 { 572 Con::warnf(ConsoleLogEntry::General, "ParticleEmitterData(%s) thetaMax > 180.0", getName()); 573 thetaMax = 180.0f; 574 } 575 if( thetaMin > thetaMax ) 576 { 577 Con::warnf(ConsoleLogEntry::General, "ParticleEmitterData(%s) thetaMin > thetaMax", getName()); 578 thetaMin = thetaMax; 579 } 580 581 if( thetaVariance > 180.0f ) 582 { 583 Con::warnf(ConsoleLogEntry::General, "ParticleEmitterData(%s) thetaVariance > 180.0", getName()); 584 thetaVariance = 180.0f; 585 } 586 587 if( thetaVariance < 0.0f ) 588 { 589 Con::warnf(ConsoleLogEntry::General, "ParticleEmitterData(%s) thetaVariance < 0.0", getName()); 590 thetaVariance = 0.0f; 591 } 592 593 if( phiVariance < 0.0f || phiVariance > 360.0f ) 594 { 595 Con::warnf(ConsoleLogEntry::General, "ParticleEmitterData(%s) invalid phiVariance", getName()); 596 phiVariance = phiVariance < 0.0f ? 0.0f : 360.0f; 597 } 598 if( thetaVariance < 0.0f || thetaVariance > 180.0f ) 599 { 600 Con::warnf(ConsoleLogEntry::General, "ParticleEmitterData(%s) invalid thetaVariance", getName()); 601 thetaVariance = thetaVariance < 0.0f ? 0.0f : 180.0f; 602 } 603 604 if ( softnessDistance < 0.0f ) 605 { 606 Con::warnf( ConsoleLogEntry::General, "ParticleEmitterData(%s) invalid softnessDistance", getName() ); 607 softnessDistance = 0.0f; 608 } 609 610 if (particleString == NULL && dataBlockIds.size() == 0) 611 { 612 Con::warnf(ConsoleLogEntry::General, "ParticleEmitterData(%s) no particleString, invalid datablock", getName()); 613 return false; 614 } 615 if (particleString && particleString[0] == '\0') 616 { 617 Con::warnf(ConsoleLogEntry::General, "ParticleEmitterData(%s) no particleString, invalid datablock", getName()); 618 return false; 619 } 620 if (particleString && dStrlen(particleString) > 255) 621 { 622 Con::errorf(ConsoleLogEntry::General, "ParticleEmitterData(%s) particle string too long [> 255 chars]", getName()); 623 return false; 624 } 625 626 if( lifetimeMS < 0 ) 627 { 628 Con::warnf(ConsoleLogEntry::General, "ParticleEmitterData(%s) lifetimeMS < 0.0f", getName()); 629 lifetimeMS = 0; 630 } 631 if( lifetimeVarianceMS > lifetimeMS ) 632 { 633 Con::warnf(ConsoleLogEntry::General, "ParticleEmitterData(%s) lifetimeVarianceMS >= lifetimeMS", getName()); 634 lifetimeVarianceMS = lifetimeMS; 635 } 636 637 638 // load the particle datablocks... 639 // 640 if( particleString != NULL ) 641 { 642 // particleString is once again a list of particle datablocks so it 643 // must be parsed to extract the particle references. 644 645 // First we parse particleString into a list of particle name tokens 646 Vector<char*> dataBlocks(__FILE__, __LINE__); 647 dsize_t tokLen = dStrlen(particleString) + 1; 648 char* tokCopy = new char[tokLen]; 649 dStrcpy(tokCopy, particleString, tokLen); 650 651 char* currTok = dStrtok(tokCopy, " \t"); 652 while (currTok != NULL) 653 { 654 dataBlocks.push_back(currTok); 655 currTok = dStrtok(NULL, " \t"); 656 } 657 if (dataBlocks.size() == 0) 658 { 659 Con::warnf(ConsoleLogEntry::General, "ParticleEmitterData(%s) invalid particles string. No datablocks found", getName()); 660 delete [] tokCopy; 661 return false; 662 } 663 664 // Now we convert the particle name tokens into particle datablocks and IDs 665 particleDataBlocks.clear(); 666 dataBlockIds.clear(); 667 668 for (U32 i = 0; i < dataBlocks.size(); i++) 669 { 670 ParticleData* pData = NULL; 671 if (Sim::findObject(dataBlocks[i], pData) == false) 672 { 673 Con::warnf(ConsoleLogEntry::General, "ParticleEmitterData(%s) unable to find particle datablock: %s", getName(), dataBlocks[i]); 674 } 675 else 676 { 677 particleDataBlocks.push_back(pData); 678 dataBlockIds.push_back(pData->getId()); 679 } 680 } 681 682 // cleanup 683 delete [] tokCopy; 684 685 // check that we actually found some particle datablocks 686 if (particleDataBlocks.size() == 0) 687 { 688 Con::warnf(ConsoleLogEntry::General, "ParticleEmitterData(%s) unable to find any particle datablocks", getName()); 689 return false; 690 } 691 } 692 693 return true; 694} 695 696//----------------------------------------------------------------------------- 697// preload 698//----------------------------------------------------------------------------- 699bool ParticleEmitterData::preload(bool server, String &errorStr) 700{ 701 if( Parent::preload(server, errorStr) == false ) 702 return false; 703 704 particleDataBlocks.clear(); 705 for (U32 i = 0; i < dataBlockIds.size(); i++) 706 { 707 ParticleData* pData = NULL; 708 if (Sim::findObject(dataBlockIds[i], pData) == false) 709 Con::warnf(ConsoleLogEntry::General, "ParticleEmitterData(%s) unable to find particle datablock: %d", getName(), dataBlockIds[i]); 710 else 711 particleDataBlocks.push_back(pData); 712 } 713 714 if (!server) 715 { 716#if defined(AFX_CAP_PARTICLE_POOLS) 717 if (do_pool_id_convert) 718 { 719 SimObjectId db_id = (SimObjectId)(uintptr_t)pool_datablock; 720 if (db_id != 0) 721 { 722 // try to convert id to pointer 723 if (!Sim::findObject(db_id, pool_datablock)) 724 { 725 Con::errorf("ParticleEmitterData::reload() -- bad datablockId: 0x%x (poolData)", db_id); 726 } 727 } 728 do_pool_id_convert = false; 729 } 730#endif 731 732 // load emitter texture if specified 733 if (textureName && textureName[0]) 734 { 735 textureHandle = GFXTexHandle(textureName, &GFXStaticTextureSRGBProfile, avar("%s() - textureHandle (line %d)", __FUNCTION__, __LINE__)); 736 if (!textureHandle) 737 { 738 errorStr = String::ToString("Missing particle emitter texture: %s", textureName); 739 return false; 740 } 741 } 742 // otherwise, check that all particles refer to the same texture 743 else if (particleDataBlocks.size() > 1) 744 { 745 StringTableEntry txr_name = particleDataBlocks[0]->textureName; 746 for (S32 i = 1; i < particleDataBlocks.size(); i++) 747 { 748 // warn if particle textures are inconsistent 749 if (particleDataBlocks[i]->textureName != txr_name) 750 { 751 Con::warnf(ConsoleLogEntry::General, "ParticleEmitterData(%s) particles reference different textures.", getName()); 752 break; 753 } 754 } 755 } 756 } 757 758 // if blend-style is undefined check legacy useInvAlpha settings 759 if (blendStyle == ParticleRenderInst::BlendUndefined && particleDataBlocks.size() > 0) 760 { 761 bool useInvAlpha = particleDataBlocks[0]->useInvAlpha; 762 for (S32 i = 1; i < particleDataBlocks.size(); i++) 763 { 764 // warn if blend-style legacy useInvAlpha settings are inconsistent 765 if (particleDataBlocks[i]->useInvAlpha != useInvAlpha) 766 { 767 Con::warnf(ConsoleLogEntry::General, "ParticleEmitterData(%s) particles have inconsistent useInvAlpha settings.", getName()); 768 break; 769 } 770 } 771 blendStyle = (useInvAlpha) ? ParticleRenderInst::BlendNormal : ParticleRenderInst::BlendAdditive; 772 } 773 774 if( !server ) 775 { 776 allocPrimBuffer(); 777 } 778 779 return true; 780} 781 782//----------------------------------------------------------------------------- 783// alloc PrimitiveBuffer 784// The datablock allocates this static index buffer because it's the same 785// for all of the emitters - each particle quad uses the same index ordering 786//----------------------------------------------------------------------------- 787void ParticleEmitterData::allocPrimBuffer( S32 overrideSize ) 788{ 789 // calculate particle list size 790 AssertFatal(particleDataBlocks.size() > 0, "Error, no particles found." ); 791 U32 maxPartLife = particleDataBlocks[0]->lifetimeMS + particleDataBlocks[0]->lifetimeVarianceMS; 792 for (S32 i = 1; i < particleDataBlocks.size(); i++) 793 { 794 U32 mpl = particleDataBlocks[i]->lifetimeMS + particleDataBlocks[i]->lifetimeVarianceMS; 795 if (mpl > maxPartLife) 796 maxPartLife = mpl; 797 } 798 799 partListInitSize = maxPartLife / (ejectionPeriodMS - periodVarianceMS); 800 partListInitSize += 8; // add 8 as "fudge factor" to make sure it doesn't realloc if it goes over by 1 801 if (parts_per_eject > 1) 802 partListInitSize *= parts_per_eject; 803 804 // if override size is specified, then the emitter overran its buffer and needs a larger allocation 805 if( overrideSize != -1 ) 806 { 807 partListInitSize = overrideSize; 808 } 809 810 // create index buffer based on that size 811 U32 indexListSize = partListInitSize * 6; // 6 indices per particle 812 U16 *indices = new U16[ indexListSize ]; 813 814 for( U32 i=0; i<partListInitSize; i++ ) 815 { 816 // this index ordering should be optimal (hopefully) for the vertex cache 817 U16 *idx = &indices[i*6]; 818 volatile U32 offset = i * 4; // set to volatile to fix VC6 Release mode compiler bug 819 idx[0] = 0 + offset; 820 idx[1] = 1 + offset; 821 idx[2] = 3 + offset; 822 idx[3] = 1 + offset; 823 idx[4] = 3 + offset; 824 idx[5] = 2 + offset; 825 } 826 827 828 U16 *ibIndices; 829 GFXBufferType bufferType = GFXBufferTypeStatic; 830 831 primBuff.set( GFX, indexListSize, 0, bufferType ); 832 primBuff.lock( &ibIndices ); 833 dMemcpy( ibIndices, indices, indexListSize * sizeof(U16) ); 834 primBuff.unlock(); 835 836 delete [] indices; 837} 838 839//#define TRACK_PARTICLE_EMITTER_DATA_CLONES 840 841#ifdef TRACK_PARTICLE_EMITTER_DATA_CLONES 842static int emitter_data_clones = 0; 843#endif 844 845ParticleEmitterData::ParticleEmitterData(const ParticleEmitterData& other, bool temp_clone) : GameBaseData(other, temp_clone) 846{ 847#ifdef TRACK_PARTICLE_EMITTER_DATA_CLONES 848 emitter_data_clones++; 849 if (emitter_data_clones == 1) 850 Con::errorf("ParticleEmitterData -- Clones are on the loose!"); 851#endif 852 853 ejectionPeriodMS = other.ejectionPeriodMS; 854 periodVarianceMS = other.periodVarianceMS; 855 ejectionVelocity = other.ejectionVelocity; 856 velocityVariance = other.velocityVariance; 857 ejectionOffset = other.ejectionOffset; 858 ejectionOffsetVariance = other.ejectionOffsetVariance; 859 thetaMin = other.thetaMin; 860 thetaMax = other.thetaMax; 861 thetaVariance = other.thetaVariance; 862 phiReferenceVel = other.phiReferenceVel; 863 phiVariance = other.phiVariance; 864 softnessDistance = other.softnessDistance; 865 ambientFactor = other.ambientFactor; 866 lifetimeMS = other.lifetimeMS; 867 lifetimeVarianceMS = other.lifetimeVarianceMS; 868 overrideAdvance = other.overrideAdvance; 869 orientParticles = other.orientParticles; 870 orientOnVelocity = other.orientOnVelocity; 871 ribbonParticles = other.ribbonParticles; 872 useEmitterSizes = other.useEmitterSizes; 873 useEmitterColors = other.useEmitterColors; 874 alignParticles = other.alignParticles; 875 alignDirection = other.alignDirection; 876 particleString = other.particleString; 877 particleDataBlocks = other.particleDataBlocks; // -- derived from particleString 878 dataBlockIds = other.dataBlockIds; // -- derived from particleString 879 partListInitSize = other.partListInitSize; // -- approx calc from other fields 880 primBuff = other.primBuff; 881 blendStyle = other.blendStyle; 882 sortParticles = other.sortParticles; 883 reverseOrder = other.reverseOrder; 884 textureName = other.textureName; 885 textureHandle = other.textureHandle; // -- TextureHandle loads using textureName 886 highResOnly = other.highResOnly; 887 glow = other.glow; 888 renderReflection = other.renderReflection; 889 fade_color = other.fade_color; 890 fade_size = other.fade_size; 891 fade_alpha = other.fade_alpha; 892 ejectionInvert = other.ejectionInvert; 893 parts_per_eject = other.parts_per_eject; // -- set to 1 (used by subclasses) 894 use_emitter_xfm = other.use_emitter_xfm; 895#if defined(AFX_CAP_PARTICLE_POOLS) 896 pool_datablock = other.pool_datablock; 897 pool_index = other.pool_index; 898 pool_depth_fade = other.pool_depth_fade; 899 pool_radial_fade = other.pool_radial_fade; 900 do_pool_id_convert = other.do_pool_id_convert; // -- flags pool id conversion need 901#endif 902} 903 904ParticleEmitterData::~ParticleEmitterData() 905{ 906 if (!isTempClone()) 907 return; 908 909 for (S32 i = 0; i < particleDataBlocks.size(); i++) 910 { 911 if (particleDataBlocks[i] && particleDataBlocks[i]->isTempClone()) 912 { 913 delete particleDataBlocks[i]; 914 particleDataBlocks[i] = 0; 915 } 916 } 917 918#ifdef TRACK_PARTICLE_EMITTER_DATA_CLONES 919 if (emitter_data_clones > 0) 920 { 921 emitter_data_clones--; 922 if (emitter_data_clones == 0) 923 Con::errorf("ParticleEmitterData -- Clones eliminated!"); 924 } 925 else 926 Con::errorf("ParticleEmitterData -- Too many clones deleted!"); 927#endif 928} 929 930ParticleEmitterData* ParticleEmitterData::cloneAndPerformSubstitutions(const SimObject* owner, S32 index) 931{ 932 if (!owner) 933 return this; 934 935 bool clone_parts_db = false; 936 937 // note -- this could be checked when the particle blocks are evaluated 938 for (S32 i = 0; i < this->particleDataBlocks.size(); i++) 939 { 940 if (this->particleDataBlocks[i] && (this->particleDataBlocks[i]->getSubstitutionCount() > 0)) 941 { 942 clone_parts_db = true; 943 break; 944 } 945 } 946 947 ParticleEmitterData* sub_emitter_db = this; 948 949 if (this->getSubstitutionCount() > 0 || clone_parts_db) 950 { 951 sub_emitter_db = new ParticleEmitterData(*this, true); 952 performSubstitutions(sub_emitter_db, owner, index); 953 954 if (clone_parts_db) 955 { 956 for (S32 i = 0; i < sub_emitter_db->particleDataBlocks.size(); i++) 957 { 958 if (sub_emitter_db->particleDataBlocks[i] && (sub_emitter_db->particleDataBlocks[i]->getSubstitutionCount() > 0)) 959 { 960 ParticleData* orig_db = sub_emitter_db->particleDataBlocks[i]; 961 sub_emitter_db->particleDataBlocks[i] = new ParticleData(*orig_db, true); 962 orig_db->performSubstitutions(sub_emitter_db->particleDataBlocks[i], owner, index); 963 } 964 } 965 } 966 } 967 968 return sub_emitter_db; 969} 970 971//----------------------------------------------------------------------------- 972// ParticleEmitter 973//----------------------------------------------------------------------------- 974ParticleEmitter::ParticleEmitter() 975{ 976 mDeleteWhenEmpty = false; 977 mDeleteOnTick = false; 978 979 mInternalClock = 0; 980 mNextParticleTime = 0; 981 982 mLastPosition.set(0, 0, 0); 983 mHasLastPosition = false; 984 985 mLifetimeMS = 0; 986 mElapsedTimeMS = 0; 987 988 part_store = 0; 989 part_freelist = NULL; 990 part_list_head.next = NULL; 991 n_part_capacity = 0; 992 n_parts = 0; 993 994 mThetaOld = 0; 995 mPhiOld = 0; 996 997 mCurBuffSize = 0; 998 999 mDead = false; 1000 mDataBlock = NULL; 1001 1002 // ParticleEmitter should be allocated on the client only. 1003 mNetFlags.set( IsGhost ); 1004 fade_amt = 1.0f; 1005 forced_bbox = false; 1006 db_temp_clone = false; 1007 pos_pe.set(0,0,0); 1008 sort_priority = 0; 1009 mDataBlock = 0; 1010 std::fill_n(sizes, ParticleData::PDC_NUM_KEYS, 0.0f); 1011#if defined(AFX_CAP_PARTICLE_POOLS) 1012 pool = 0; 1013#endif 1014} 1015 1016//----------------------------------------------------------------------------- 1017// destructor 1018//----------------------------------------------------------------------------- 1019ParticleEmitter::~ParticleEmitter() 1020{ 1021 for( S32 i = 0; i < part_store.size(); i++ ) 1022 { 1023 delete [] part_store[i]; 1024 } 1025 if (db_temp_clone && mDataBlock && mDataBlock->isTempClone()) 1026 { 1027 for (S32 i = 0; i < mDataBlock->particleDataBlocks.size(); i++) 1028 { 1029 if (mDataBlock->particleDataBlocks[i] && mDataBlock->particleDataBlocks[i]->isTempClone()) 1030 { 1031 delete mDataBlock->particleDataBlocks[i]; 1032 mDataBlock->particleDataBlocks[i] = 0; 1033 } 1034 } 1035 delete mDataBlock; 1036 mDataBlock = 0; 1037 } 1038} 1039 1040//----------------------------------------------------------------------------- 1041// onAdd 1042//----------------------------------------------------------------------------- 1043bool ParticleEmitter::onAdd() 1044{ 1045 if( !Parent::onAdd() ) 1046 return false; 1047 1048 // add to client side mission cleanup 1049 SimGroup *cleanup = dynamic_cast<SimGroup *>( Sim::findObject( "ClientMissionCleanup") ); 1050 if( cleanup != NULL ) 1051 { 1052 cleanup->addObject( this ); 1053 } 1054 1055 removeFromProcessList(); 1056 1057 F32 radius = 5.0; 1058 mObjBox.minExtents = Point3F(-radius, -radius, -radius); 1059 mObjBox.maxExtents = Point3F(radius, radius, radius); 1060 resetWorldBox(); 1061 1062#if defined(AFX_CAP_PARTICLE_POOLS) 1063 if (pool) 1064 pool->addParticleEmitter(this); 1065#endif 1066 1067 return true; 1068} 1069 1070 1071//----------------------------------------------------------------------------- 1072// onRemove 1073//----------------------------------------------------------------------------- 1074void ParticleEmitter::onRemove() 1075{ 1076#if defined(AFX_CAP_PARTICLE_POOLS) 1077 if (pool) 1078 { 1079 pool->removeParticleEmitter(this); 1080 pool = 0; 1081 } 1082#endif 1083 1084 removeFromScene(); 1085 Parent::onRemove(); 1086} 1087 1088 1089//----------------------------------------------------------------------------- 1090// onNewDataBlock 1091//----------------------------------------------------------------------------- 1092bool ParticleEmitter::onNewDataBlock( GameBaseData *dptr, bool reload ) 1093{ 1094 mDataBlock = dynamic_cast<ParticleEmitterData*>( dptr ); 1095 if ( !mDataBlock || !Parent::onNewDataBlock( dptr, reload ) ) 1096 return false; 1097 1098 mLifetimeMS = mDataBlock->lifetimeMS; 1099 if( mDataBlock->lifetimeVarianceMS ) 1100 { 1101 mLifetimeMS += S32( gRandGen.randI() % (2 * mDataBlock->lifetimeVarianceMS + 1)) - S32(mDataBlock->lifetimeVarianceMS ); 1102 } 1103 1104 // Allocate particle structures and init the freelist. Member part_store 1105 // is a Vector so that we can allocate more particles if partListInitSize 1106 // turns out to be too small. 1107 // 1108 if (mDataBlock->partListInitSize > 0) 1109 { 1110 for( S32 i = 0; i < part_store.size(); i++ ) 1111 { 1112 delete [] part_store[i]; 1113 } 1114 part_store.clear(); 1115 n_part_capacity = mDataBlock->partListInitSize; 1116 Particle* store_block = new Particle[n_part_capacity]; 1117 part_store.push_back(store_block); 1118 part_freelist = store_block; 1119 Particle* last_part = part_freelist; 1120 Particle* part = last_part+1; 1121 for( S32 i = 1; i < n_part_capacity; i++, part++, last_part++ ) 1122 { 1123 last_part->next = part; 1124 } 1125 store_block[n_part_capacity-1].next = NULL; 1126 part_list_head.next = NULL; 1127 n_parts = 0; 1128 } 1129 if (mDataBlock->isTempClone()) 1130 { 1131 db_temp_clone = true; 1132 return true; 1133 } 1134 1135 scriptOnNewDataBlock(); 1136 return true; 1137} 1138 1139//----------------------------------------------------------------------------- 1140// getCollectiveColor 1141//----------------------------------------------------------------------------- 1142LinearColorF ParticleEmitter::getCollectiveColor() 1143{ 1144 U32 count = 0; 1145 LinearColorF color = LinearColorF(0.0f, 0.0f, 0.0f); 1146 1147 count = n_parts; 1148 for( Particle* part = part_list_head.next; part != NULL; part = part->next ) 1149 { 1150 color += part->color; 1151 } 1152 1153 if(count > 0) 1154 { 1155 color /= F32(count); 1156 } 1157 1158 //if(color.red == 0.0f && color.green == 0.0f && color.blue == 0.0f) 1159 // color = color; 1160 1161 return color; 1162} 1163 1164 1165//----------------------------------------------------------------------------- 1166// prepRenderImage 1167//----------------------------------------------------------------------------- 1168void ParticleEmitter::prepRenderImage(SceneRenderState* state) 1169{ 1170#if defined(AFX_CAP_PARTICLE_POOLS) 1171 if (pool) 1172 return; 1173#endif 1174 1175 if( state->isReflectPass() && !getDataBlock()->renderReflection ) 1176 return; 1177 1178 // Never render into shadows. 1179 if (state->isShadowPass()) 1180 return; 1181 1182 PROFILE_SCOPE(ParticleEmitter_prepRenderImage); 1183 1184 if ( mDead || 1185 n_parts == 0 || 1186 part_list_head.next == NULL ) 1187 return; 1188 1189 RenderPassManager *renderManager = state->getRenderPass(); 1190 const Point3F &camPos = state->getCameraPosition(); 1191 copyToVB( camPos, state->getAmbientLightColor() ); 1192 1193 if (!mVertBuff.isValid()) 1194 return; 1195 1196 ParticleRenderInst *ri = renderManager->allocInst<ParticleRenderInst>(); 1197 1198 ri->vertBuff = &mVertBuff; 1199 ri->primBuff = &getDataBlock()->primBuff; 1200 ri->translucentSort = true; 1201 ri->type = RenderPassManager::RIT_Particle; 1202 ri->sortDistSq = getRenderWorldBox().getSqDistanceToPoint( camPos ); 1203 ri->defaultKey = (-sort_priority*100); 1204 1205 // Draw the system offscreen unless the highResOnly flag is set on the datablock 1206 ri->systemState = ( getDataBlock()->highResOnly ? PSS_AwaitingHighResDraw : PSS_AwaitingOffscreenDraw ); 1207 1208 ri->modelViewProj = renderManager->allocUniqueXform( GFX->getProjectionMatrix() * 1209 GFX->getViewMatrix() * 1210 GFX->getWorldMatrix() ); 1211 1212 // Update position on the matrix before multiplying it 1213 mBBObjToWorld.setPosition(mLastPosition); 1214 1215 ri->bbModelViewProj = renderManager->allocUniqueXform( *ri->modelViewProj * mBBObjToWorld ); 1216 1217 ri->wsPosition = getWorldTransform().getPosition(); 1218 1219 ri->count = n_parts; 1220 1221 ri->blendStyle = mDataBlock->blendStyle; 1222 1223 ri->glow = mDataBlock->glow; 1224 1225 // use first particle's texture unless there is an emitter texture to override it 1226 if (mDataBlock->textureHandle) 1227 ri->diffuseTex = &*(mDataBlock->textureHandle); 1228 else 1229 ri->diffuseTex = &*(part_list_head.next->dataBlock->textureHandle); 1230 1231 ri->softnessDistance = mDataBlock->softnessDistance; 1232 1233 // Sort by texture too. 1234 ri->defaultKey = ri->diffuseTex ? (uintptr_t)ri->diffuseTex : (uintptr_t)ri->vertBuff; 1235 1236 renderManager->addInst( ri ); 1237 1238} 1239 1240//----------------------------------------------------------------------------- 1241// setSizes 1242//----------------------------------------------------------------------------- 1243void ParticleEmitter::setSizes( F32 *sizeList ) 1244{ 1245 for( S32 i=0; i<ParticleData::PDC_NUM_KEYS; i++ ) 1246 { 1247 sizes[i] = sizeList[i]; 1248 } 1249} 1250 1251//----------------------------------------------------------------------------- 1252// setColors 1253//----------------------------------------------------------------------------- 1254void ParticleEmitter::setColors( LinearColorF *colorList ) 1255{ 1256 for( S32 i=0; i<ParticleData::PDC_NUM_KEYS; i++ ) 1257 { 1258 colors[i] = colorList[i]; 1259 } 1260} 1261 1262//----------------------------------------------------------------------------- 1263// deleteWhenEmpty 1264//----------------------------------------------------------------------------- 1265void ParticleEmitter::deleteWhenEmpty() 1266{ 1267 // if the following asserts fire, there is a reasonable chance that you are trying to delete a particle emitter 1268 // that has already been deleted (possibly by ClientMissionCleanup). If so, use a SimObjectPtr to the emitter and check it 1269 // for null before calling this function. 1270 AssertFatal(isProperlyAdded(), "ParticleEmitter must be registed before calling deleteWhenEmpty"); 1271 AssertFatal(!mDead, "ParticleEmitter already deleted"); 1272 AssertFatal(!isDeleted(), "ParticleEmitter already deleted"); 1273 AssertFatal(!isRemoved(), "ParticleEmitter already removed"); 1274 1275 // this check is for non debug case, so that we don't write in to freed memory 1276 bool okToDelete = !mDead && isProperlyAdded() && !isDeleted() && !isRemoved(); 1277 if (okToDelete) 1278 { 1279 mDeleteWhenEmpty = true; 1280 if( !n_parts ) 1281 { 1282 // We're already empty, so delete us now. 1283 1284 mDead = true; 1285 deleteObject(); 1286 } 1287 else 1288 AssertFatal( getSceneManager() != NULL, "ParticleEmitter not on process list and won't get ticked to death" ); 1289 } 1290} 1291 1292//----------------------------------------------------------------------------- 1293// emitParticles 1294//----------------------------------------------------------------------------- 1295void ParticleEmitter::emitParticles(const Point3F& point, 1296 const bool useLastPosition, 1297 const Point3F& axis, 1298 const Point3F& velocity, 1299 const U32 numMilliseconds) 1300{ 1301 if( mDead ) return; 1302 1303 // lifetime over - no more particles 1304 if( mLifetimeMS > 0 && mElapsedTimeMS > mLifetimeMS ) 1305 { 1306 return; 1307 } 1308 1309 pos_pe = point; 1310 Point3F realStart; 1311 if( useLastPosition && mHasLastPosition ) 1312 realStart = mLastPosition; 1313 else 1314 realStart = point; 1315 1316 emitParticles(realStart, point, 1317 axis, 1318 velocity, 1319 numMilliseconds); 1320} 1321 1322//----------------------------------------------------------------------------- 1323// emitParticles 1324//----------------------------------------------------------------------------- 1325void ParticleEmitter::emitParticles(const Point3F& start, 1326 const Point3F& end, 1327 const Point3F& axis, 1328 const Point3F& velocity, 1329 const U32 numMilliseconds) 1330{ 1331 if( mDead ) return; 1332 1333 if( mDataBlock->particleDataBlocks.empty() ) 1334 return; 1335 1336 // lifetime over - no more particles 1337 if( mLifetimeMS > 0 && mElapsedTimeMS > mLifetimeMS ) 1338 { 1339 return; 1340 } 1341 1342 U32 currTime = 0; 1343 bool particlesAdded = false; 1344 1345 Point3F axisx; 1346 if( mFabs(axis.z) < 0.9f ) 1347 mCross(axis, Point3F(0, 0, 1), &axisx); 1348 else 1349 mCross(axis, Point3F(0, 1, 0), &axisx); 1350 axisx.normalize(); 1351 1352 if( mNextParticleTime != 0 ) 1353 { 1354 // Need to handle next particle 1355 // 1356 if( mNextParticleTime > numMilliseconds ) 1357 { 1358 // Defer to next update 1359 // (Note that this introduces a potential spatial irregularity if the owning 1360 // object is accelerating, and updating at a low frequency) 1361 // 1362 mNextParticleTime -= numMilliseconds; 1363 mInternalClock += numMilliseconds; 1364 mLastPosition = end; 1365 mHasLastPosition = true; 1366 return; 1367 } 1368 else 1369 { 1370 currTime += mNextParticleTime; 1371 mInternalClock += mNextParticleTime; 1372 // Emit particle at curr time 1373 1374 // Create particle at the correct position 1375 Point3F pos; 1376 pos.interpolate(start, end, F32(currTime) / F32(numMilliseconds)); 1377 addParticle(pos, axis, velocity, axisx, numMilliseconds-currTime); 1378 1379 particlesAdded = true; 1380 mNextParticleTime = 0; 1381 } 1382 } 1383 1384 while( currTime < numMilliseconds ) 1385 { 1386 S32 nextTime = mDataBlock->ejectionPeriodMS; 1387 if( mDataBlock->periodVarianceMS != 0 ) 1388 { 1389 nextTime += S32(gRandGen.randI() % (2 * mDataBlock->periodVarianceMS + 1)) - 1390 S32(mDataBlock->periodVarianceMS); 1391 } 1392 AssertFatal(nextTime > 0, "Error, next particle ejection time must always be greater than 0"); 1393 1394 if( currTime + nextTime > numMilliseconds ) 1395 { 1396 mNextParticleTime = (currTime + nextTime) - numMilliseconds; 1397 mInternalClock += numMilliseconds - currTime; 1398 AssertFatal(mNextParticleTime > 0, "Error, should not have deferred this particle!"); 1399 break; 1400 } 1401 1402 currTime += nextTime; 1403 mInternalClock += nextTime; 1404 1405 // Create particle at the correct position 1406 Point3F pos; 1407 pos.interpolate(start, end, F32(currTime) / F32(numMilliseconds)); 1408 addParticle(pos, axis, velocity, axisx, numMilliseconds-currTime); 1409 particlesAdded = true; 1410 1411 // This override-advance code is restored in order to correctly adjust 1412 // animated parameters of particles allocated within the same frame 1413 // update. Note that ordering is important and this code correctly 1414 // adds particles in the same newest-to-oldest ordering of the link-list. 1415 // 1416 // NOTE: We are assuming that the just added particle is at the head of our 1417 // list. If that changes, so must this... 1418 U32 advanceMS = numMilliseconds - currTime; 1419 if (mDataBlock->overrideAdvance == false && advanceMS != 0) 1420 { 1421 Particle* last_part = part_list_head.next; 1422 if (advanceMS > last_part->totalLifetime) 1423 { 1424 part_list_head.next = last_part->next; 1425 n_parts--; 1426 last_part->next = part_freelist; 1427 part_freelist = last_part; 1428 } 1429 else 1430 { 1431 if (advanceMS != 0) 1432 { 1433 F32 t = F32(advanceMS) / 1000.0; 1434 1435 Point3F a = last_part->acc; 1436 a -= last_part->vel * last_part->dataBlock->dragCoefficient; 1437 a += mWindVelocity * last_part->dataBlock->windCoefficient; 1438 //a += Point3F(0.0f, 0.0f, -9.81f) * last_part->dataBlock->gravityCoefficient; 1439 a.z += -9.81f*last_part->dataBlock->gravityCoefficient; // as long as gravity is a constant, this is faster 1440 1441 last_part->vel += a * t; 1442 //last_part->pos += last_part->vel * t; 1443 last_part->pos_local += last_part->vel * t; 1444 1445 // AFX -- allow subclasses to adjust the particle params here 1446 sub_particleUpdate(last_part); 1447 1448 if (last_part->dataBlock->constrain_pos) 1449 last_part->pos = last_part->pos_local + this->pos_pe; 1450 else 1451 last_part->pos = last_part->pos_local; 1452 1453 updateKeyData( last_part ); 1454 } 1455 } 1456 } 1457 } 1458 1459 // DMMFIX: Lame and slow... 1460 if( particlesAdded == true ) 1461 updateBBox(); 1462 1463 1464 if( n_parts > 0 && getSceneManager() == NULL ) 1465 { 1466 gClientSceneGraph->addObjectToScene(this); 1467 ClientProcessList::get()->addObject(this); 1468 } 1469 1470 mLastPosition = end; 1471 mHasLastPosition = true; 1472} 1473 1474//----------------------------------------------------------------------------- 1475// emitParticles 1476//----------------------------------------------------------------------------- 1477void ParticleEmitter::emitParticles(const Point3F& rCenter, 1478 const Point3F& rNormal, 1479 const F32 radius, 1480 const Point3F& velocity, 1481 S32 count) 1482{ 1483 if( mDead ) return; 1484 1485 // lifetime over - no more particles 1486 if( mLifetimeMS > 0 && mElapsedTimeMS > mLifetimeMS ) 1487 { 1488 return; 1489 } 1490 1491 1492 Point3F axisx, axisy; 1493 Point3F axisz = rNormal; 1494 1495 if( axisz.isZero() ) 1496 { 1497 axisz.set( 0.0, 0.0, 1.0 ); 1498 } 1499 1500 if( mFabs(axisz.z) < 0.98 ) 1501 { 1502 mCross(axisz, Point3F(0, 0, 1), &axisy); 1503 axisy.normalize(); 1504 } 1505 else 1506 { 1507 mCross(axisz, Point3F(0, 1, 0), &axisy); 1508 axisy.normalize(); 1509 } 1510 mCross(axisz, axisy, &axisx); 1511 axisx.normalize(); 1512 1513 // Should think of a better way to distribute the 1514 // particles within the hemisphere. 1515 for( S32 i = 0; i < count; i++ ) 1516 { 1517 Point3F pos = axisx * (radius * (1 - (2 * gRandGen.randF()))); 1518 pos += axisy * (radius * (1 - (2 * gRandGen.randF()))); 1519 pos += axisz * (radius * gRandGen.randF()); 1520 1521 Point3F axis = pos; 1522 axis.normalize(); 1523 pos += rCenter; 1524 1525 addParticle(pos, axis, velocity, axisz, 0); 1526 } 1527 1528 // Set world bounding box 1529 mObjBox.minExtents = rCenter - Point3F(radius, radius, radius); 1530 mObjBox.maxExtents = rCenter + Point3F(radius, radius, radius); 1531 resetWorldBox(); 1532 1533 // Make sure we're part of the world 1534 if( n_parts > 0 && getSceneManager() == NULL ) 1535 { 1536 gClientSceneGraph->addObjectToScene(this); 1537 ClientProcessList::get()->addObject(this); 1538 } 1539 1540 mHasLastPosition = false; 1541} 1542 1543//----------------------------------------------------------------------------- 1544// updateBBox - SLOW, bad news 1545//----------------------------------------------------------------------------- 1546void ParticleEmitter::updateBBox() 1547{ 1548 if (forced_bbox) 1549 return; 1550 Point3F minPt(1e10, 1e10, 1e10); 1551 Point3F maxPt(-1e10, -1e10, -1e10); 1552 1553 for (Particle* part = part_list_head.next; part != NULL; part = part->next) 1554 { 1555 Point3F particleSize(part->size * 0.5f, 0.0f, part->size * 0.5f); 1556 minPt.setMin( part->pos - particleSize ); 1557 maxPt.setMax( part->pos + particleSize ); 1558 } 1559 1560 mObjBox = Box3F(minPt, maxPt); 1561 MatrixF temp = getTransform(); 1562 setTransform(temp); 1563 1564 mBBObjToWorld.identity(); 1565 Point3F boxScale = mObjBox.getExtents(); 1566 boxScale.x = getMax(boxScale.x, 1.0f); 1567 boxScale.y = getMax(boxScale.y, 1.0f); 1568 boxScale.z = getMax(boxScale.z, 1.0f); 1569 mBBObjToWorld.scale(boxScale); 1570 1571#if defined(AFX_CAP_PARTICLE_POOLS) 1572 if (pool) 1573 pool->updatePoolBBox(this); 1574#endif 1575} 1576 1577//----------------------------------------------------------------------------- 1578// addParticle 1579//----------------------------------------------------------------------------- 1580void ParticleEmitter::addParticle(const Point3F& pos, const Point3F& axis, const Point3F& vel, 1581 const Point3F& axisx, const U32 age_offset) 1582{ 1583 n_parts++; 1584 if (n_parts > n_part_capacity || n_parts > mDataBlock->partListInitSize) 1585 { 1586 // In an emergency we allocate additional particles in blocks of 16. 1587 // This should happen rarely. 1588 Particle* store_block = new Particle[16]; 1589 part_store.push_back(store_block); 1590 n_part_capacity += 16; 1591 for (S32 i = 0; i < 16; i++) 1592 { 1593 store_block[i].next = part_freelist; 1594 part_freelist = &store_block[i]; 1595 } 1596 mDataBlock->allocPrimBuffer(n_part_capacity); // allocate larger primitive buffer or will crash 1597 } 1598 Particle* pNew = part_freelist; 1599 part_freelist = pNew->next; 1600 pNew->next = part_list_head.next; 1601 part_list_head.next = pNew; 1602 1603 // for earlier access to constrain_pos, the ParticleData datablock is chosen here instead 1604 // of later in the method. 1605 U32 dBlockIndex = gRandGen.randI() % mDataBlock->particleDataBlocks.size(); 1606 ParticleData* part_db = mDataBlock->particleDataBlocks[dBlockIndex]; 1607 // set start position to world or local space 1608 Point3F pos_start; 1609 if (part_db->constrain_pos) 1610 pos_start.set(0,0,0); 1611 else 1612 pos_start = pos; 1613 Point3F ejectionAxis = axis; 1614 F32 theta = 0.0f; 1615 F32 thetaTarget = (mDataBlock->thetaMax + mDataBlock->thetaMin) / 2.0f; 1616 if (mDataBlock->thetaVariance <= 0.0f) 1617 theta = (mDataBlock->thetaMax - mDataBlock->thetaMin) * gRandGen.randF() + mDataBlock->thetaMin; 1618 else 1619 { 1620 F32 thetaDelta = ( gRandGen.randF() - 0.5f) * mDataBlock->thetaVariance * 2.0f; 1621 thetaDelta += ( (thetaTarget - mThetaOld) / mDataBlock->thetaMax ) * mDataBlock->thetaVariance * 0.25f; 1622 theta = mThetaOld + thetaDelta; 1623 } 1624 mThetaOld = theta; 1625 1626 F32 ref = (F32(mInternalClock) / 1000.0) * mDataBlock->phiReferenceVel; 1627 F32 phi = 0.0f; 1628 if (mDataBlock->thetaVariance <= 0.0f) 1629 { 1630 phi = ref + gRandGen.randF() * mDataBlock->phiVariance; 1631 } 1632 else 1633 { 1634 F32 phiDelta = (gRandGen.randF() - 0.5f) * mDataBlock->thetaVariance * 2.0f; 1635 phi = ref + mPhiOld + phiDelta; 1636 if (phi > mDataBlock->phiVariance) 1637 phi += fabs(phiDelta) * -2.0f; 1638 if (phi < 0.0f) 1639 phi += fabs(phiDelta) * 2.0f; 1640 } 1641 mPhiOld = phi; 1642 1643 // Both phi and theta are in degs. Create axis angles out of them, and create the 1644 // appropriate rotation matrix... 1645 AngAxisF thetaRot(axisx, theta * (M_PI / 180.0)); 1646 AngAxisF phiRot(axis, phi * (M_PI / 180.0)); 1647 1648 MatrixF temp(true); 1649 thetaRot.setMatrix(&temp); 1650 temp.mulP(ejectionAxis); 1651 phiRot.setMatrix(&temp); 1652 temp.mulP(ejectionAxis); 1653 1654 F32 initialVel = mDataBlock->ejectionVelocity; 1655 initialVel += (mDataBlock->velocityVariance * 2.0f * gRandGen.randF()) - mDataBlock->velocityVariance; 1656 1657 pNew->pos = pos_start + (ejectionAxis * (mDataBlock->ejectionOffset + mDataBlock->ejectionOffsetVariance* gRandGen.randF()) ); 1658 pNew->pos_local = pNew->pos; 1659 pNew->vel = mDataBlock->ejectionInvert ? ejectionAxis * -initialVel : ejectionAxis * initialVel; 1660 if (mDataBlock->orientParticles) 1661 pNew->orientDir = ejectionAxis; 1662 else 1663 // note -- for non-oriented particles, we use orientDir.x to store the billboard start angle. 1664 pNew->orientDir.x = mDegToRad(part_db->start_angle + part_db->angle_variance*2.0f*gRandGen.randF() - part_db->angle_variance); 1665 pNew->acc.set(0, 0, 0); 1666 pNew->currentAge = age_offset; 1667 pNew->t_last = 0.0f; 1668 // ribbon particles only use the first particle 1669 if(mDataBlock->ribbonParticles) 1670 { 1671 mDataBlock->particleDataBlocks[0]->initializeParticle(pNew, vel); 1672 } 1673 else 1674 { 1675 U32 dBlockIndex = gRandGen.randI() % mDataBlock->particleDataBlocks.size(); 1676 mDataBlock->particleDataBlocks[dBlockIndex]->initializeParticle(pNew, vel); 1677 } 1678 updateKeyData( pNew ); 1679 1680} 1681 1682 1683//----------------------------------------------------------------------------- 1684// processTick 1685//----------------------------------------------------------------------------- 1686void ParticleEmitter::processTick(const Move*) 1687{ 1688 if( mDeleteOnTick == true ) 1689 { 1690 mDead = true; 1691 deleteObject(); 1692 } 1693} 1694 1695 1696//----------------------------------------------------------------------------- 1697// advanceTime 1698//----------------------------------------------------------------------------- 1699void ParticleEmitter::advanceTime(F32 dt) 1700{ 1701 if( dt < 0.00001 ) return; 1702 1703 Parent::advanceTime(dt); 1704 1705 if( dt > 0.5 ) dt = 0.5; 1706 1707 if( mDead ) return; 1708 1709 mElapsedTimeMS += (S32)(dt * 1000.0f); 1710 1711 U32 numMSToUpdate = (U32)(dt * 1000.0f); 1712 if( numMSToUpdate == 0 ) return; 1713 1714 // TODO: Prefetch 1715 1716 // remove dead particles 1717 Particle* last_part = &part_list_head; 1718 for (Particle* part = part_list_head.next; part != NULL; part = part->next) 1719 { 1720 part->currentAge += numMSToUpdate; 1721 if (part->currentAge > part->totalLifetime) 1722 { 1723 n_parts--; 1724 last_part->next = part->next; 1725 part->next = part_freelist; 1726 part_freelist = part; 1727 part = last_part; 1728 } 1729 else 1730 { 1731 last_part = part; 1732 } 1733 } 1734 1735 AssertFatal( n_parts >= 0, "ParticleEmitter: negative part count!" ); 1736 1737 if (n_parts < 1 && mDeleteWhenEmpty) 1738 { 1739 mDeleteOnTick = true; 1740 return; 1741 } 1742 1743 if( numMSToUpdate != 0 && n_parts > 0 ) 1744 { 1745 update( numMSToUpdate ); 1746 } 1747} 1748 1749//----------------------------------------------------------------------------- 1750// Update key related particle data 1751//----------------------------------------------------------------------------- 1752void ParticleEmitter::updateKeyData( Particle *part ) 1753{ 1754 //Ensure that our lifetime is never below 0 1755 if( part->totalLifetime < 1 ) 1756 part->totalLifetime = 1; 1757 1758 if (part->currentAge > part->totalLifetime) 1759 part->currentAge = part->totalLifetime; 1760 F32 t = (F32)part->currentAge / (F32)part->totalLifetime; 1761 1762 1763 for( U32 i = 1; i < ParticleData::PDC_NUM_KEYS; i++ ) 1764 { 1765 if( part->dataBlock->times[i] >= t ) 1766 { 1767 F32 firstPart = t - part->dataBlock->times[i-1]; 1768 F32 total = part->dataBlock->times[i] - 1769 part->dataBlock->times[i-1]; 1770 1771 firstPart /= total; 1772 1773 if( mDataBlock->useEmitterColors ) 1774 { 1775 part->color.interpolate(colors[i-1], colors[i], firstPart); 1776 } 1777 else 1778 { 1779 part->color.interpolate(part->dataBlock->colors[i-1], 1780 part->dataBlock->colors[i], 1781 firstPart); 1782 } 1783 1784 if( mDataBlock->useEmitterSizes ) 1785 { 1786 part->size = (sizes[i-1] * (1.0 - firstPart)) + 1787 (sizes[i] * firstPart); 1788 } 1789 else 1790 { 1791 part->size = (part->dataBlock->sizes[i-1] * (1.0 - firstPart)) + 1792 (part->dataBlock->sizes[i] * firstPart); 1793 part->size *= part->dataBlock->sizeBias; 1794 } 1795 1796 if (mDataBlock->fade_color) 1797 { 1798 if (mDataBlock->fade_alpha) 1799 part->color *= fade_amt; 1800 else 1801 { 1802 part->color.red *= fade_amt; 1803 part->color.green *= fade_amt; 1804 part->color.blue *= fade_amt; 1805 } 1806 } 1807 else if (mDataBlock->fade_alpha) 1808 part->color.alpha *= fade_amt; 1809 1810 if (mDataBlock->fade_size) 1811 part->size *= fade_amt; 1812 break; 1813 1814 } 1815 } 1816} 1817 1818//----------------------------------------------------------------------------- 1819// Update particles 1820//----------------------------------------------------------------------------- 1821// AFX CODE BLOCK (enhanced-emitter) << 1822void ParticleEmitter::update( U32 ms ) 1823{ 1824 F32 t = F32(ms)/1000.0f; // AFX -- moved outside loop, no need to recalculate this for every particle 1825 1826 for (Particle* part = part_list_head.next; part != NULL; part = part->next) 1827 { 1828 Point3F a = part->acc; 1829 a -= part->vel * part->dataBlock->dragCoefficient; 1830 a += mWindVelocity * part->dataBlock->windCoefficient; 1831 a.z += -9.81f*part->dataBlock->gravityCoefficient; // AFX -- as long as gravity is a constant, this is faster 1832 1833 part->vel += a * t; 1834 part->pos_local += part->vel * t; 1835 1836 // AFX -- allow subclasses to adjust the particle params here 1837 sub_particleUpdate(part); 1838 1839 if (part->dataBlock->constrain_pos) 1840 part->pos = part->pos_local + this->pos_pe; 1841 else 1842 part->pos = part->pos_local; 1843 1844 updateKeyData( part ); 1845 } 1846} 1847 1848//----------------------------------------------------------------------------- 1849// Copy particles to vertex buffer 1850//----------------------------------------------------------------------------- 1851 1852// structure used for particle sorting. 1853struct SortParticle 1854{ 1855 Particle* p; 1856 F32 k; 1857}; 1858 1859// qsort callback function for particle sorting 1860S32 QSORT_CALLBACK cmpSortParticles(const void* p1, const void* p2) 1861{ 1862 const SortParticle* sp1 = (const SortParticle*)p1; 1863 const SortParticle* sp2 = (const SortParticle*)p2; 1864 1865 if (sp2->k > sp1->k) 1866 return 1; 1867 else if (sp2->k == sp1->k) 1868 return 0; 1869 else 1870 return -1; 1871} 1872 1873void ParticleEmitter::copyToVB( const Point3F &camPos, const LinearColorF &ambientColor ) 1874{ 1875 static Vector<SortParticle> orderedVector(__FILE__, __LINE__); 1876 1877 PROFILE_START(ParticleEmitter_copyToVB); 1878 1879 PROFILE_START(ParticleEmitter_copyToVB_Sort); 1880 // build sorted list of particles (far to near) 1881 if (mDataBlock->sortParticles) 1882 { 1883 orderedVector.clear(); 1884 1885 MatrixF modelview = GFX->getWorldMatrix(); 1886 Point3F viewvec; modelview.getRow(1, &viewvec); 1887 1888 // add each particle and a distance based sort key to orderedVector 1889 for (Particle* pp = part_list_head.next; pp != NULL; pp = pp->next) 1890 { 1891 orderedVector.increment(); 1892 orderedVector.last().p = pp; 1893 orderedVector.last().k = mDot(pp->pos, viewvec); 1894 } 1895 1896 // qsort the list into far to near ordering 1897 dQsort(orderedVector.address(), orderedVector.size(), sizeof(SortParticle), cmpSortParticles); 1898 } 1899 PROFILE_END(); 1900 1901 static Vector<ParticleVertexType> tempBuff(2048); 1902 tempBuff.reserve( n_parts*4 + 64); // make sure tempBuff is big enough 1903 ParticleVertexType *buffPtr = tempBuff.address(); // use direct pointer (faster) 1904 1905 if (mDataBlock->ribbonParticles) 1906 { 1907 PROFILE_START(ParticleEmitter_copyToVB_Ribbon); 1908 1909 if (mDataBlock->reverseOrder) 1910 { 1911 buffPtr += 4 * (n_parts - 1); 1912 // do sorted-oriented particles 1913 if (mDataBlock->sortParticles) 1914 { 1915 SortParticle* partPtr = orderedVector.address(); 1916 for (U32 i = 0; i < n_parts - 1; i++, partPtr++, buffPtr -= 4) 1917 setupRibbon(partPtr->p, partPtr++->p, partPtr--->p, camPos, ambientColor, buffPtr); 1918 } 1919 // do unsorted-oriented particles 1920 else 1921 { 1922 Particle* oldPtr = NULL; 1923 for (Particle* partPtr = part_list_head.next; partPtr != NULL; partPtr = partPtr->next, buffPtr -= 4) { 1924 setupRibbon(partPtr, partPtr->next, oldPtr, camPos, ambientColor, buffPtr); 1925 oldPtr = partPtr; 1926 } 1927 } 1928 } 1929 else 1930 { 1931 // do sorted-oriented particles 1932 if (mDataBlock->sortParticles) 1933 { 1934 SortParticle* partPtr = orderedVector.address(); 1935 for (U32 i = 0; i < n_parts - 1; i++, partPtr++, buffPtr += 4) 1936 setupRibbon(partPtr->p, partPtr++->p, partPtr--->p, camPos, ambientColor, buffPtr); 1937 } 1938 // do unsorted-oriented particles 1939 else 1940 { 1941 Particle* oldPtr = NULL; 1942 for (Particle* partPtr = part_list_head.next; partPtr != NULL; partPtr = partPtr->next, buffPtr += 4) { 1943 setupRibbon(partPtr, partPtr->next, oldPtr, camPos, ambientColor, buffPtr); 1944 oldPtr = partPtr; 1945 } 1946 } 1947 } 1948 PROFILE_END(); 1949 } 1950 else if (mDataBlock->orientParticles) 1951 { 1952 PROFILE_START(ParticleEmitter_copyToVB_Orient); 1953 1954 if (mDataBlock->reverseOrder) 1955 { 1956 buffPtr += 4*(n_parts-1); 1957 // do sorted-oriented particles 1958 if (mDataBlock->sortParticles) 1959 { 1960 SortParticle* partPtr = orderedVector.address(); 1961 for (U32 i = 0; i < n_parts; i++, partPtr++, buffPtr-=4 ) 1962 setupOriented(partPtr->p, camPos, ambientColor, buffPtr); 1963 } 1964 // do unsorted-oriented particles 1965 else 1966 { 1967 for (Particle* partPtr = part_list_head.next; partPtr != NULL; partPtr = partPtr->next, buffPtr-=4) 1968 setupOriented(partPtr, camPos, ambientColor, buffPtr); 1969 } 1970 } 1971 else 1972 { 1973 // do sorted-oriented particles 1974 if (mDataBlock->sortParticles) 1975 { 1976 SortParticle* partPtr = orderedVector.address(); 1977 for (U32 i = 0; i < n_parts; i++, partPtr++, buffPtr+=4 ) 1978 setupOriented(partPtr->p, camPos, ambientColor, buffPtr); 1979 } 1980 // do unsorted-oriented particles 1981 else 1982 { 1983 for (Particle* partPtr = part_list_head.next; partPtr != NULL; partPtr = partPtr->next, buffPtr+=4) 1984 setupOriented(partPtr, camPos, ambientColor, buffPtr); 1985 } 1986 } 1987 PROFILE_END(); 1988 } 1989 else if (mDataBlock->alignParticles) 1990 { 1991 PROFILE_START(ParticleEmitter_copyToVB_Aligned); 1992 1993 if (mDataBlock->reverseOrder) 1994 { 1995 buffPtr += 4*(n_parts-1); 1996 1997 // do sorted-oriented particles 1998 if (mDataBlock->sortParticles) 1999 { 2000 SortParticle* partPtr = orderedVector.address(); 2001 for (U32 i = 0; i < n_parts; i++, partPtr++, buffPtr-=4 ) 2002 setupAligned(partPtr->p, ambientColor, buffPtr); 2003 } 2004 // do unsorted-oriented particles 2005 else 2006 { 2007 Particle *partPtr = part_list_head.next; 2008 for (; partPtr != NULL; partPtr = partPtr->next, buffPtr-=4) 2009 setupAligned(partPtr, ambientColor, buffPtr); 2010 } 2011 } 2012 else 2013 { 2014 // do sorted-oriented particles 2015 if (mDataBlock->sortParticles) 2016 { 2017 SortParticle* partPtr = orderedVector.address(); 2018 for (U32 i = 0; i < n_parts; i++, partPtr++, buffPtr+=4 ) 2019 setupAligned(partPtr->p, ambientColor, buffPtr); 2020 } 2021 // do unsorted-oriented particles 2022 else 2023 { 2024 Particle *partPtr = part_list_head.next; 2025 for (; partPtr != NULL; partPtr = partPtr->next, buffPtr+=4) 2026 setupAligned(partPtr, ambientColor, buffPtr); 2027 } 2028 } 2029 PROFILE_END(); 2030 } 2031 else 2032 { 2033 PROFILE_START(ParticleEmitter_copyToVB_NonOriented); 2034 // somewhat odd ordering so that texture coordinates match the oriented 2035 // particles 2036 Point3F basePoints[4]; 2037 basePoints[0] = Point3F(-1.0, 0.0, 1.0); 2038 basePoints[1] = Point3F(-1.0, 0.0, -1.0); 2039 basePoints[2] = Point3F( 1.0, 0.0, -1.0); 2040 basePoints[3] = Point3F( 1.0, 0.0, 1.0); 2041 2042 MatrixF camView = GFX->getWorldMatrix(); 2043 camView.transpose(); // inverse - this gets the particles facing camera 2044 2045 if (mDataBlock->reverseOrder) 2046 { 2047 buffPtr += 4*(n_parts-1); 2048 // do sorted-billboard particles 2049 if (mDataBlock->sortParticles) 2050 { 2051 SortParticle *partPtr = orderedVector.address(); 2052 for( U32 i=0; i<n_parts; i++, partPtr++, buffPtr-=4 ) 2053 setupBillboard( partPtr->p, basePoints, camView, ambientColor, buffPtr ); 2054 } 2055 // do unsorted-billboard particles 2056 else 2057 { 2058 for (Particle* partPtr = part_list_head.next; partPtr != NULL; partPtr = partPtr->next, buffPtr-=4) 2059 setupBillboard( partPtr, basePoints, camView, ambientColor, buffPtr ); 2060 } 2061 } 2062 else 2063 { 2064 // do sorted-billboard particles 2065 if (mDataBlock->sortParticles) 2066 { 2067 SortParticle *partPtr = orderedVector.address(); 2068 for( U32 i=0; i<n_parts; i++, partPtr++, buffPtr+=4 ) 2069 setupBillboard( partPtr->p, basePoints, camView, ambientColor, buffPtr ); 2070 } 2071 // do unsorted-billboard particles 2072 else 2073 { 2074 for (Particle* partPtr = part_list_head.next; partPtr != NULL; partPtr = partPtr->next, buffPtr+=4) 2075 setupBillboard( partPtr, basePoints, camView, ambientColor, buffPtr ); 2076 } 2077 } 2078 2079 PROFILE_END(); 2080 } 2081 2082 PROFILE_START(ParticleEmitter_copyToVB_LockCopy); 2083 // create new VB if emitter size grows 2084 if( !mVertBuff || n_parts > mCurBuffSize ) 2085 { 2086 mCurBuffSize = n_parts; 2087 mVertBuff.set( GFX, n_parts * 4, GFXBufferTypeDynamic ); 2088 } 2089 // lock and copy tempBuff to video RAM 2090 ParticleVertexType *verts = mVertBuff.lock(); 2091 dMemcpy( verts, tempBuff.address(), n_parts * 4 * sizeof(ParticleVertexType) ); 2092 mVertBuff.unlock(); 2093 PROFILE_END(); 2094 2095 PROFILE_END(); 2096} 2097 2098//----------------------------------------------------------------------------- 2099// Set up particle for billboard style render 2100//----------------------------------------------------------------------------- 2101void ParticleEmitter::setupBillboard( Particle *part, 2102 Point3F *basePts, 2103 const MatrixF &camView, 2104 const LinearColorF &ambientColor, 2105 ParticleVertexType *lVerts ) 2106{ 2107 F32 width = part->size * 0.5f; 2108 F32 spinAngle = part->spinSpeed * part->currentAge * AgedSpinToRadians; 2109 2110 F32 sy, cy; 2111 mSinCos(spinAngle, sy, cy); 2112 2113 const F32 ambientLerp = mClampF( mDataBlock->ambientFactor, 0.0f, 1.0f ); 2114 LinearColorF partCol = mLerp( part->color, ( part->color * ambientColor ), ambientLerp ); 2115 2116 // fill four verts, use macro and unroll loop 2117 #define fillVert(){ \ 2118 lVerts->point.x = cy * basePts->x - sy * basePts->z; \ 2119 lVerts->point.y = 0.0f; \ 2120 lVerts->point.z = sy * basePts->x + cy * basePts->z; \ 2121 camView.mulV( lVerts->point ); \ 2122 lVerts->point *= width; \ 2123 lVerts->point += part->pos; \ 2124 lVerts->color = partCol.toColorI(); } \ 2125 2126 // Here we deal with UVs for animated particle (billboard) 2127 if (part->dataBlock->animateTexture && !part->dataBlock->animTexFrames.empty()) 2128 { 2129 S32 fm = (S32)(part->currentAge*(1.0/1000.0)*part->dataBlock->framesPerSec); 2130 U8 fm_tile = part->dataBlock->animTexFrames[fm % part->dataBlock->numFrames]; 2131 S32 uv[4]; 2132 uv[0] = fm_tile + fm_tile/part->dataBlock->animTexTiling.x; 2133 uv[1] = uv[0] + (part->dataBlock->animTexTiling.x + 1); 2134 uv[2] = uv[1] + 1; 2135 uv[3] = uv[0] + 1; 2136 2137 fillVert(); 2138 // Here and below, we copy UVs from particle datablock's current frame's UVs (billboard) 2139 lVerts->texCoord = part->dataBlock->animTexUVs[uv[0]]; 2140 ++lVerts; 2141 ++basePts; 2142 2143 fillVert(); 2144 lVerts->texCoord = part->dataBlock->animTexUVs[uv[1]]; 2145 ++lVerts; 2146 ++basePts; 2147 2148 fillVert(); 2149 lVerts->texCoord = part->dataBlock->animTexUVs[uv[2]]; 2150 ++lVerts; 2151 ++basePts; 2152 2153 fillVert(); 2154 lVerts->texCoord = part->dataBlock->animTexUVs[uv[3]]; 2155 ++lVerts; 2156 ++basePts; 2157 2158 return; 2159 } 2160 2161 fillVert(); 2162 // Here and below, we copy UVs from particle datablock's texCoords (billboard) 2163 lVerts->texCoord = part->dataBlock->texCoords[0]; 2164 ++lVerts; 2165 ++basePts; 2166 2167 fillVert(); 2168 lVerts->texCoord = part->dataBlock->texCoords[1]; 2169 ++lVerts; 2170 ++basePts; 2171 2172 fillVert(); 2173 lVerts->texCoord = part->dataBlock->texCoords[2]; 2174 ++lVerts; 2175 ++basePts; 2176 2177 fillVert(); 2178 lVerts->texCoord = part->dataBlock->texCoords[3]; 2179 ++lVerts; 2180 ++basePts; 2181} 2182 2183//----------------------------------------------------------------------------- 2184// Set up oriented particle 2185//----------------------------------------------------------------------------- 2186void ParticleEmitter::setupOriented( Particle *part, 2187 const Point3F &camPos, 2188 const LinearColorF &ambientColor, 2189 ParticleVertexType *lVerts ) 2190{ 2191 Point3F dir; 2192 2193 if( mDataBlock->orientOnVelocity ) 2194 { 2195 // don't render oriented particle if it has no velocity 2196 if( part->vel.magnitudeSafe() == 0.0 ) return; 2197 dir = part->vel; 2198 } 2199 else 2200 { 2201 dir = part->orientDir; 2202 } 2203 2204 Point3F dirFromCam = part->pos - camPos; 2205 Point3F crossDir; 2206 mCross( dirFromCam, dir, &crossDir ); 2207 crossDir.normalize(); 2208 dir.normalize(); 2209 2210 F32 width = part->size * 0.5f; 2211 dir *= width; 2212 crossDir *= width; 2213 Point3F start = part->pos - dir; 2214 Point3F end = part->pos + dir; 2215 2216 const F32 ambientLerp = mClampF( mDataBlock->ambientFactor, 0.0f, 1.0f ); 2217 LinearColorF partCol = mLerp( part->color, ( part->color * ambientColor ), ambientLerp ); 2218 const ColorI color = partCol.toColorI(); 2219 // Here we deal with UVs for animated particle (oriented) 2220 if (part->dataBlock->animateTexture) 2221 { 2222 // Let particle compute the UV indices for current frame 2223 S32 fm = (S32)(part->currentAge*(1.0f/1000.0f)*part->dataBlock->framesPerSec); 2224 U8 fm_tile = part->dataBlock->animTexFrames[fm % part->dataBlock->numFrames]; 2225 S32 uv[4]; 2226 uv[0] = fm_tile + fm_tile/part->dataBlock->animTexTiling.x; 2227 uv[1] = uv[0] + (part->dataBlock->animTexTiling.x + 1); 2228 uv[2] = uv[1] + 1; 2229 uv[3] = uv[0] + 1; 2230 2231 lVerts->point = start + crossDir; 2232 lVerts->color = color; 2233 // Here and below, we copy UVs from particle datablock's current frame's UVs (oriented) 2234 lVerts->texCoord = part->dataBlock->animTexUVs[uv[0]]; 2235 ++lVerts; 2236 2237 lVerts->point = start - crossDir; 2238 lVerts->color = color; 2239 lVerts->texCoord = part->dataBlock->animTexUVs[uv[1]]; 2240 ++lVerts; 2241 2242 lVerts->point = end - crossDir; 2243 lVerts->color = color; 2244 lVerts->texCoord = part->dataBlock->animTexUVs[uv[2]]; 2245 ++lVerts; 2246 2247 lVerts->point = end + crossDir; 2248 lVerts->color = color; 2249 lVerts->texCoord = part->dataBlock->animTexUVs[uv[3]]; 2250 ++lVerts; 2251 2252 return; 2253 } 2254 2255 lVerts->point = start + crossDir; 2256 lVerts->color = color; 2257 // Here and below, we copy UVs from particle datablock's texCoords (oriented) 2258 lVerts->texCoord = part->dataBlock->texCoords[1]; 2259 ++lVerts; 2260 2261 lVerts->point = start - crossDir; 2262 lVerts->color = color; 2263 lVerts->texCoord = part->dataBlock->texCoords[2]; 2264 ++lVerts; 2265 2266 lVerts->point = end - crossDir; 2267 lVerts->color = color; 2268 lVerts->texCoord = part->dataBlock->texCoords[3]; 2269 ++lVerts; 2270 2271 lVerts->point = end + crossDir; 2272 lVerts->color = color; 2273 lVerts->texCoord = part->dataBlock->texCoords[0]; 2274 ++lVerts; 2275} 2276 2277void ParticleEmitter::setupAligned( const Particle *part, 2278 const LinearColorF &ambientColor, 2279 ParticleVertexType *lVerts ) 2280{ 2281 // The aligned direction will always be normalized. 2282 Point3F dir = mDataBlock->alignDirection; 2283 2284 // Find a right vector for this particle. 2285 Point3F right; 2286 if (mFabs(dir.y) > mFabs(dir.z)) 2287 mCross(Point3F::UnitZ, dir, &right); 2288 else 2289 mCross(Point3F::UnitY, dir, &right); 2290 right.normalize(); 2291 2292 // If we have a spin velocity. 2293 if ( !mIsZero( part->spinSpeed ) ) 2294 { 2295 F32 spinAngle = part->spinSpeed * part->currentAge * AgedSpinToRadians; 2296 2297 // This is an inline quaternion vector rotation which 2298 // is faster that QuatF.mulP(), but generates different 2299 // results and hence cannot replace it right now. 2300 2301 F32 sin, qw; 2302 mSinCos( spinAngle * 0.5f, sin, qw ); 2303 F32 qx = dir.x * sin; 2304 F32 qy = dir.y * sin; 2305 F32 qz = dir.z * sin; 2306 2307 F32 vx = ( right.x * qw ) + ( right.z * qy ) - ( right.y * qz ); 2308 F32 vy = ( right.y * qw ) + ( right.x * qz ) - ( right.z * qx ); 2309 F32 vz = ( right.z * qw ) + ( right.y * qx ) - ( right.x * qy ); 2310 F32 vw = ( right.x * qx ) + ( right.y * qy ) + ( right.z * qz ); 2311 2312 right.x = ( qw * vx ) + ( qx * vw ) + ( qy * vz ) - ( qz * vy ); 2313 right.y = ( qw * vy ) + ( qy * vw ) + ( qz * vx ) - ( qx * vz ); 2314 right.z = ( qw * vz ) + ( qz * vw ) + ( qx * vy ) - ( qy * vx ); 2315 } 2316 2317 // Get the cross vector. 2318 Point3F cross; 2319 mCross(right, dir, &cross); 2320 2321 F32 width = part->size * 0.5f; 2322 right *= width; 2323 cross *= width; 2324 Point3F start = part->pos - right; 2325 Point3F end = part->pos + right; 2326 2327 const F32 ambientLerp = mClampF( mDataBlock->ambientFactor, 0.0f, 1.0f ); 2328 LinearColorF partCol = mLerp( part->color, ( part->color * ambientColor ), ambientLerp ); 2329 const ColorI color = partCol.toColorI(); 2330 // Here we deal with UVs for animated particle 2331 if (part->dataBlock->animateTexture) 2332 { 2333 // Let particle compute the UV indices for current frame 2334 S32 fm = (S32)(part->currentAge*(1.0f/1000.0f)*part->dataBlock->framesPerSec); 2335 U8 fm_tile = part->dataBlock->animTexFrames[fm % part->dataBlock->numFrames]; 2336 S32 uv[4]; 2337 uv[0] = fm_tile + fm_tile/part->dataBlock->animTexTiling.x; 2338 uv[1] = uv[0] + (part->dataBlock->animTexTiling.x + 1); 2339 uv[2] = uv[1] + 1; 2340 uv[3] = uv[0] + 1; 2341 2342 lVerts->point = start + cross; 2343 lVerts->color = color; 2344 lVerts->texCoord = part->dataBlock->animTexUVs[uv[0]]; 2345 ++lVerts; 2346 2347 lVerts->point = start - cross; 2348 lVerts->color = color; 2349 lVerts->texCoord = part->dataBlock->animTexUVs[uv[1]]; 2350 ++lVerts; 2351 2352 lVerts->point = end - cross; 2353 lVerts->color = color; 2354 lVerts->texCoord = part->dataBlock->animTexUVs[uv[2]]; 2355 ++lVerts; 2356 2357 lVerts->point = end + cross; 2358 lVerts->color = color; 2359 lVerts->texCoord = part->dataBlock->animTexUVs[uv[3]]; 2360 ++lVerts; 2361 } 2362 else 2363 { 2364 // Here and below, we copy UVs from particle datablock's texCoords 2365 lVerts->point = start + cross; 2366 lVerts->color = color; 2367 lVerts->texCoord = part->dataBlock->texCoords[0]; 2368 ++lVerts; 2369 2370 lVerts->point = start - cross; 2371 lVerts->color = color; 2372 lVerts->texCoord = part->dataBlock->texCoords[1]; 2373 ++lVerts; 2374 2375 lVerts->point = end - cross; 2376 lVerts->color = color; 2377 lVerts->texCoord = part->dataBlock->texCoords[2]; 2378 ++lVerts; 2379 2380 lVerts->point = end + cross; 2381 lVerts->color = color; 2382 lVerts->texCoord = part->dataBlock->texCoords[3]; 2383 ++lVerts; 2384 } 2385} 2386 2387void ParticleEmitter::setupRibbon(Particle *part, 2388 Particle *next, 2389 Particle *prev, 2390 const Point3F &camPos, 2391 const LinearColorF &ambientColor, 2392 ParticleVertexType *lVerts) 2393{ 2394 Point3F dir, dirFromCam; 2395 Point3F crossDir, crossDirNext; 2396 Point3F start, end; 2397 LinearColorF prevCol; 2398 static Point3F crossDirPrev; 2399 static int position; 2400 static F32 alphaMod, alphaModEnd; 2401 2402 const F32 ambientLerp = mClampF(mDataBlock->ambientFactor, 0.0f, 1.0f); 2403 LinearColorF partCol = mLerp(part->color, (part->color * ambientColor), ambientLerp); 2404 if (part->currentAge > part->totalLifetime) 2405 { 2406 F32 alphaDeath = (part->currentAge - part->totalLifetime) / 200.0f; 2407 if (alphaDeath > 1.0f) 2408 alphaDeath = 1.0f; 2409 alphaDeath = 1.0f - alphaDeath; 2410 partCol.alpha *= alphaDeath; 2411 } 2412 2413 start = part->pos; 2414 position++; 2415 2416 if (next == NULL && prev == NULL) { 2417 // a ribbon of just one particle 2418 position = 0; 2419 2420 if (part->vel.magnitudeSafe() == 0.0) 2421 dir = part->orientDir; 2422 else 2423 dir = part->vel; 2424 2425 dir.normalize(); 2426 dirFromCam = part->pos - camPos; 2427 mCross(dirFromCam, dir, &crossDir); 2428 crossDir.normalize(); 2429 crossDir = crossDir * part->size * 0.5; 2430 crossDirPrev = crossDir; 2431 2432 partCol.alpha = 0.0f; 2433 prevCol = partCol; 2434 end = part->pos; 2435 } 2436 else if (next == NULL && prev != NULL) 2437 { 2438 // last link in the chain, also the oldest 2439 dir = part->pos - prev->pos; 2440 dir.normalize(); 2441 dirFromCam = part->pos - camPos; 2442 mCross(dirFromCam, dir, &crossDir); 2443 crossDir.normalize(); 2444 crossDir = crossDir * part->size * 0.5; 2445 2446 end = prev->pos; 2447 partCol.alpha = 0.0f; 2448 prevCol = mLerp(prev->color, (prev->color * ambientColor), ambientLerp); 2449 prevCol.alpha *= alphaModEnd; 2450 } 2451 else if (next != NULL && prev == NULL) 2452 { 2453 // first link in chain, newest particle 2454 // since we draw from current to previous, this one isn't drawn 2455 position = 0; 2456 2457 dir = next->pos - part->pos; 2458 dir.normalize(); 2459 2460 dirFromCam = part->pos - camPos; 2461 mCross(dirFromCam, dir, &crossDir); 2462 crossDir.normalize(); 2463 crossDir = crossDir * part->size * 0.5f; 2464 crossDirPrev = crossDir; 2465 2466 partCol.alpha = 0.0f; 2467 prevCol = partCol; 2468 alphaModEnd = 0.0f; 2469 2470 end = part->pos; 2471 } 2472 else 2473 { 2474 // middle of chain 2475 dir = next->pos - prev->pos; 2476 dir.normalize(); 2477 dirFromCam = part->pos - camPos; 2478 mCross(dirFromCam, dir, &crossDir); 2479 crossDir.normalize(); 2480 2481 crossDir = crossDir * part->size * 0.5; 2482 2483 prevCol = mLerp(prev->color, (prev->color * ambientColor), ambientLerp); 2484 2485 if (position == 1) 2486 { 2487 // the second particle has a few tweaks for alpha, to smoothly match the first particle 2488 // we only want to do this once when the particle first fades in, and avoid a strobing effect 2489 alphaMod = (float(part->currentAge) / float(part->currentAge - prev->currentAge)) - 1.0f; 2490 if (alphaMod > 1.0f) 2491 alphaMod = 1.0f; 2492 partCol.alpha *= alphaMod; 2493 prevCol.alpha = 0.0f; 2494 if (next->next == NULL) 2495 alphaModEnd = alphaMod; 2496 //Con::printf("alphaMod: %f", alphaMod ); 2497 } 2498 else if (position == 2) 2499 { 2500 prevCol.alpha *= alphaMod; 2501 alphaMod = 0.0f; 2502 } 2503 2504 if (next->next == NULL && position > 1) 2505 { 2506 // next to last particle, start the fade out 2507 alphaModEnd = (float(next->totalLifetime - next->currentAge)) / (float(part->totalLifetime - part->currentAge)); 2508 alphaModEnd *= 2.0f; 2509 if (alphaModEnd > 1.0f) 2510 alphaModEnd = 1.0f; 2511 partCol.alpha *= alphaModEnd; 2512 //Con::printf("alphaMod: %f Lifetime: %d Age: %d", alphaMod, part->totalLifetime, part->currentAge ); 2513 } 2514 end = prev->pos; 2515 } 2516 2517 ColorI pCol = partCol.toColorI(); 2518 2519 // Here we deal with UVs for animated particle (oriented) 2520 if (part->dataBlock->animateTexture) 2521 { 2522 // Let particle compute the UV indices for current frame 2523 S32 fm = (S32)(part->currentAge*(1.0f / 1000.0f)*part->dataBlock->framesPerSec); 2524 U8 fm_tile = part->dataBlock->animTexFrames[fm % part->dataBlock->numFrames]; 2525 S32 uv[4]; 2526 uv[0] = fm_tile + fm_tile / part->dataBlock->animTexTiling.x; 2527 uv[1] = uv[0] + (part->dataBlock->animTexTiling.x + 1); 2528 uv[2] = uv[1] + 1; 2529 uv[3] = uv[0] + 1; 2530 2531 lVerts->point = start + crossDir; 2532 lVerts->color = pCol; 2533 // Here and below, we copy UVs from particle datablock's current frame's UVs (oriented) 2534 lVerts->texCoord = part->dataBlock->animTexUVs[uv[0]]; 2535 ++lVerts; 2536 2537 lVerts->point = start - crossDir; 2538 lVerts->color = pCol; 2539 lVerts->texCoord = part->dataBlock->animTexUVs[uv[1]]; 2540 ++lVerts; 2541 2542 lVerts->point = end - crossDirPrev; 2543 lVerts->color = pCol; 2544 lVerts->texCoord = part->dataBlock->animTexUVs[uv[2]]; 2545 ++lVerts; 2546 2547 lVerts->point = end + crossDirPrev; 2548 lVerts->color = pCol; 2549 lVerts->texCoord = part->dataBlock->animTexUVs[uv[3]]; 2550 ++lVerts; 2551 2552 crossDirPrev = crossDir; 2553 return; 2554 } 2555 2556 lVerts->point = start + crossDir; 2557 lVerts->color = pCol; 2558 // Here and below, we copy UVs from particle datablock's texCoords (oriented) 2559 lVerts->texCoord = part->dataBlock->texCoords[0]; 2560 ++lVerts; 2561 2562 lVerts->point = start - crossDir; 2563 lVerts->color = pCol; 2564 lVerts->texCoord = part->dataBlock->texCoords[1]; 2565 ++lVerts; 2566 2567 lVerts->point = end - crossDirPrev; 2568 lVerts->color = pCol; 2569 lVerts->texCoord = part->dataBlock->texCoords[2]; 2570 ++lVerts; 2571 2572 lVerts->point = end + crossDirPrev; 2573 lVerts->color = pCol; 2574 lVerts->texCoord = part->dataBlock->texCoords[3]; 2575 ++lVerts; 2576 2577 crossDirPrev = crossDir; 2578} 2579 2580bool ParticleEmitterData::reload() 2581{ 2582 // Clear out current particle data. 2583 2584 dataBlockIds.clear(); 2585 particleDataBlocks.clear(); 2586 2587 // Parse out particle string. 2588 2589 U32 numUnits = 0; 2590 if( particleString ) 2591 numUnits = StringUnit::getUnitCount( particleString, " \t" ); 2592 if( !particleString || !particleString[ 0 ] || !numUnits ) 2593 { 2594 Con::errorf( "ParticleEmitterData(%s) has an empty particles string.", getName() ); 2595 mReloadSignal.trigger(); 2596 return false; 2597 } 2598 2599 for( U32 i = 0; i < numUnits; ++ i ) 2600 { 2601 const char* dbName = StringUnit::getUnit( particleString, i, " \t" ); 2602 2603 ParticleData* data = NULL; 2604 if( !Sim::findObject( dbName, data ) ) 2605 { 2606 Con::errorf( ConsoleLogEntry::General, "ParticleEmitterData(%s) unable to find particle datablock: %s", getName(), dbName ); 2607 continue; 2608 } 2609 2610 particleDataBlocks.push_back( data ); 2611 dataBlockIds.push_back( data->getId() ); 2612 } 2613 2614 // Check that we actually found some particle datablocks. 2615 2616 if( particleDataBlocks.empty() ) 2617 { 2618 Con::errorf( ConsoleLogEntry::General, "ParticleEmitterData(%s) unable to find any particle datablocks", getName() ); 2619 mReloadSignal.trigger(); 2620 return false; 2621 } 2622 2623 // Trigger reload. 2624 2625 mReloadSignal.trigger(); 2626 2627 return true; 2628} 2629 2630DefineEngineMethod(ParticleEmitterData, reload, void,(),, 2631 "Reloads the ParticleData datablocks and other fields used by this emitter.\n" 2632 "@tsexample\n" 2633 "// Get the editor's current particle emitter\n" 2634 "%emitter = PE_EmitterEditor.currEmitter\n\n" 2635 "// Change a field value\n" 2636 "%emitter.setFieldValue( %propertyField, %value );\n\n" 2637 "// Reload this emitter\n" 2638 "%emitter.reload();\n" 2639 "@endtsexample\n") 2640{ 2641 object->reload(); 2642} 2643void ParticleEmitter::emitParticlesExt(const MatrixF& xfm, const Point3F& point, 2644 const Point3F& velocity, const U32 numMilliseconds) 2645{ 2646 if (mDataBlock->use_emitter_xfm) 2647 { 2648 Point3F zero_point(0.0f, 0.0f, 0.0f); 2649 this->pos_pe = zero_point; 2650 this->setTransform(xfm); 2651 Point3F axis(0.0,0.0,1.0); 2652 xfm.mulV(axis); 2653 emitParticles(zero_point, true, axis, velocity, numMilliseconds); 2654 } 2655 else 2656 { 2657 this->pos_pe = point; 2658 Point3F axis(0.0,0.0,1.0); 2659 xfm.mulV(axis); 2660 emitParticles(point, true, axis, velocity, numMilliseconds); 2661 } 2662} 2663 2664void ParticleEmitter::setForcedObjBox(Box3F& box) 2665{ 2666 mObjBox = box; 2667 forced_bbox = true; 2668#if defined(AFX_CAP_PARTICLE_POOLS) 2669 if (pool) 2670 pool->updatePoolBBox(this); 2671#endif 2672} 2673 2674void ParticleEmitter::setSortPriority(S8 priority) 2675{ 2676 sort_priority = (priority == 0) ? 1 : priority; 2677#if defined(AFX_CAP_PARTICLE_POOLS) 2678 if (pool) 2679 pool->setSortPriority(sort_priority); 2680#endif 2681} 2682 2683