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; } \
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 simple " "particle emitting object in the scene, use 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 source axis or velocity over the " "course of a single update, so emitParticles should be called at 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 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 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 <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>" " 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; } \
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 simple " "particle emitting object in the scene, use 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 source axis or velocity over the " "course of a single update, so emitParticles should be called at 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 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 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 <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>" " 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#include "platform/platform.h" 25#include "T3D/fx/particleEmitter.h" 26 27#include "scene/sceneManager.h" 28#include "scene/sceneRenderState.h" 29#include "console/consoleTypes.h" 30#include "console/typeValidators.h" 31#include "core/stream/bitStream.h" 32#include "core/strings/stringUnit.h" 33#include "math/mRandom.h" 34#include "gfx/gfxDevice.h" 35#include "gfx/primBuilder.h" 36#include "gfx/gfxStringEnumTranslate.h" 37#include "renderInstance/renderPassManager.h" 38#include "T3D/gameBase/gameProcess.h" 39#include "lighting/lightInfo.h" 40#include "console/engineAPI.h" 41 42#if defined(TORQUE_OS_XENON) 43# include "gfx/D3D9/360/gfx360MemVertexBuffer.h" 44#endif 45 46Point3F ParticleEmitter::mWindVelocity( 0.0, 0.0, 0.0 ); 47const F32 ParticleEmitter::AgedSpinToRadians = (1.0f/1000.0f) * (1.0f/360.0f) * M_PI_F * 2.0f; 48 49IMPLEMENT_CO_DATABLOCK_V1(ParticleEmitterData); 50IMPLEMENT_CONOBJECT(ParticleEmitter); 51 52ConsoleDocClass( ParticleEmitter, 53 "@brief This object is responsible for spawning particles.\n\n" 54 55 "@note This class is not normally instantiated directly - to place a simple " 56 "particle emitting object in the scene, use a ParticleEmitterNode instead.\n\n" 57 58 "This class is the main interface for creating particles - though it is " 59 "usually only accessed from within another object like ParticleEmitterNode " 60 "or WheeledVehicle. If using this object class (via C++) directly, be aware " 61 "that it does <b>not</b> track changes in source axis or velocity over the " 62 "course of a single update, so emitParticles should be called at a fairly " 63 "fine grain. The emitter will potentially track the last particle to be " 64 "created into the next call to this function in order to create a uniformly " 65 "random time distribution of the particles.\n\n" 66 67 "If the object to which the emitter is attached is in motion, it should try " 68 "to ensure that for call (n+1) to this function, start is equal to the end " 69 "from call (n). This will ensure a uniform spatial distribution.\n\n" 70 71 "@ingroup FX\n" 72 "@see ParticleEmitterData\n" 73 "@see ParticleEmitterNode\n" 74); 75 76ConsoleDocClass( ParticleEmitterData, 77 "@brief Defines particle emission properties such as ejection angle, period " 78 "and velocity for a ParticleEmitter.\n\n" 79 80 "@tsexample\n" 81 "datablock ParticleEmitterData( GrenadeExpDustEmitter )\n" 82 "{\n" 83 " ejectionPeriodMS = 1;\n" 84 " periodVarianceMS = 0;\n" 85 " ejectionVelocity = 15;\n" 86 " velocityVariance = 0.0;\n" 87 " ejectionOffset = 0.0;\n" 88 " thetaMin = 85;\n" 89 " thetaMax = 85;\n" 90 " phiReferenceVel = 0;\n" 91 " phiVariance = 360;\n" 92 " overrideAdvance = false;\n" 93 " lifetimeMS = 200;\n" 94 " particles = \"GrenadeExpDust\";\n" 95 "};\n" 96 "@endtsexample\n\n" 97 98 "@ingroup FX\n" 99 "@see ParticleEmitter\n" 100 "@see ParticleData\n" 101 "@see ParticleEmitterNode\n" 102); 103 104static const F32 sgDefaultEjectionOffset = 0.f; 105static const F32 sgDefaultPhiReferenceVel = 0.f; 106static const F32 sgDefaultPhiVariance = 360.f; 107 108//----------------------------------------------------------------------------- 109// ParticleEmitterData 110//----------------------------------------------------------------------------- 111ParticleEmitterData::ParticleEmitterData() 112{ 113 VECTOR_SET_ASSOCIATION(particleDataBlocks); 114 VECTOR_SET_ASSOCIATION(dataBlockIds); 115 116 ejectionPeriodMS = 100; // 10 Particles Per second 117 periodVarianceMS = 0; // exactly 118 119 ejectionVelocity = 2.0f; // From 1.0 - 3.0 meters per sec 120 velocityVariance = 1.0f; 121 ejectionOffset = sgDefaultEjectionOffset; // ejection from the emitter point 122 ejectionOffsetVariance = 0.0f; 123 124 thetaMin = 0.0f; // All heights 125 thetaMax = 90.0f; 126 127 phiReferenceVel = sgDefaultPhiReferenceVel; // All directions 128 phiVariance = sgDefaultPhiVariance; 129 130 softnessDistance = 1.0f; 131 ambientFactor = 0.0f; 132 133 lifetimeMS = 0; 134 lifetimeVarianceMS = 0; 135 136 overrideAdvance = true; 137 orientParticles = false; 138 orientOnVelocity = true; 139 useEmitterSizes = false; 140 useEmitterColors = false; 141 particleString = NULL; 142 partListInitSize = 0; 143 144 // These members added for support of user defined blend factors 145 // and optional particle sorting. 146 blendStyle = ParticleRenderInst::BlendUndefined; 147 sortParticles = false; 148 renderReflection = true; 149 glow = false; 150 reverseOrder = false; 151 textureName = 0; 152 textureHandle = 0; 153 highResOnly = true; 154 155 alignParticles = false; 156 alignDirection = Point3F(0.0f, 1.0f, 0.0f); 157} 158 159 160 161// Enum tables used for fields blendStyle, srcBlendFactor, dstBlendFactor. 162// Note that the enums for srcBlendFactor and dstBlendFactor are consistent 163// with the blending enums used in Torque Game Builder. 164 165typedef ParticleRenderInst::BlendStyle ParticleBlendStyle; 166DefineEnumType( ParticleBlendStyle ); 167 168ImplementEnumType( ParticleBlendStyle, 169 "The type of visual blending style to apply to the particles.\n" 170 "@ingroup FX\n\n") 171 { ParticleRenderInst::BlendNormal, "NORMAL", "No blending style.\n" }, 172 { ParticleRenderInst::BlendAdditive, "ADDITIVE", "Adds the color of the pixel to the frame buffer with full alpha for each pixel.\n" }, 173 { ParticleRenderInst::BlendSubtractive, "SUBTRACTIVE", "Subtractive Blending. Reverses the color model, causing dark colors to have a stronger visual effect.\n" }, 174 { ParticleRenderInst::BlendPremultAlpha, "PREMULTALPHA", "Color blends with the colors of the imagemap rather than the alpha.\n" }, 175EndImplementEnumType; 176 177IRangeValidator ejectPeriodIValidator(1, 2047); 178IRangeValidator periodVarianceIValidator(0, 2047); 179FRangeValidator ejectionFValidator(0.f, 655.35f); 180FRangeValidator velVarianceFValidator(0.f, 163.83f); 181FRangeValidator thetaFValidator(0.f, 180.f); 182FRangeValidator phiFValidator(0.f, 360.f); 183 184//----------------------------------------------------------------------------- 185// initPersistFields 186//----------------------------------------------------------------------------- 187void ParticleEmitterData::initPersistFields() 188{ 189 addGroup( "ParticleEmitterData" ); 190 191 addFieldV("ejectionPeriodMS", TYPEID< S32 >(), Offset(ejectionPeriodMS, ParticleEmitterData), &ejectPeriodIValidator, 192 "Time (in milliseconds) between each particle ejection." ); 193 194 addFieldV("periodVarianceMS", TYPEID< S32 >(), Offset(periodVarianceMS, ParticleEmitterData), &periodVarianceIValidator, 195 "Variance in ejection period, from 1 - ejectionPeriodMS." ); 196 197 addFieldV( "ejectionVelocity", TYPEID< F32 >(), Offset(ejectionVelocity, ParticleEmitterData), &ejectionFValidator, 198 "Particle ejection velocity." ); 199 200 addFieldV( "velocityVariance", TYPEID< F32 >(), Offset(velocityVariance, ParticleEmitterData), &velVarianceFValidator, 201 "Variance for ejection velocity, from 0 - ejectionVelocity." ); 202 203 addFieldV( "ejectionOffset", TYPEID< F32 >(), Offset(ejectionOffset, ParticleEmitterData), &ejectionFValidator, 204 "Distance along ejection Z axis from which to eject particles." ); 205 206 addFieldV( "ejectionOffsetVariance", TYPEID< F32 >(), Offset(ejectionOffsetVariance, ParticleEmitterData), &ejectionFValidator, 207 "Distance Padding along ejection Z axis from which to eject particles." ); 208 209 addFieldV( "thetaMin", TYPEID< F32 >(), Offset(thetaMin, ParticleEmitterData), &thetaFValidator, 210 "Minimum angle, from the horizontal plane, to eject from." ); 211 212 addFieldV( "thetaMax", TYPEID< F32 >(), Offset(thetaMax, ParticleEmitterData), &thetaFValidator, 213 "Maximum angle, from the horizontal plane, to eject particles from." ); 214 215 addFieldV( "phiReferenceVel", TYPEID< F32 >(), Offset(phiReferenceVel, ParticleEmitterData), &phiFValidator, 216 "Reference angle, from the vertical plane, to eject particles from." ); 217 218 addFieldV( "phiVariance", TYPEID< F32 >(), Offset(phiVariance, ParticleEmitterData), &phiFValidator, 219 "Variance from the reference angle, from 0 - 360." ); 220 221 addField( "softnessDistance", TYPEID< F32 >(), Offset(softnessDistance, ParticleEmitterData), 222 "For soft particles, the distance (in meters) where particles will be " 223 "faded based on the difference in depth between the particle and the " 224 "scene geometry." ); 225 226 addField( "ambientFactor", TYPEID< F32 >(), Offset(ambientFactor, ParticleEmitterData), 227 "Used to generate the final particle color by controlling interpolation " 228 "between the particle color and the particle color multiplied by the " 229 "ambient light color." ); 230 231 addField( "overrideAdvance", TYPEID< bool >(), Offset(overrideAdvance, ParticleEmitterData), 232 "If false, particles emitted in the same frame have their positions " 233 "adjusted. If true, adjustment is skipped and particles will clump " 234 "together." ); 235 236 addField( "orientParticles", TYPEID< bool >(), Offset(orientParticles, ParticleEmitterData), 237 "If true, Particles will always face the camera." ); 238 239 addField( "orientOnVelocity", TYPEID< bool >(), Offset(orientOnVelocity, ParticleEmitterData), 240 "If true, particles will be oriented to face in the direction they are moving." ); 241 242 addField( "particles", TYPEID< StringTableEntry >(), Offset(particleString, ParticleEmitterData), 243 "@brief List of space or TAB delimited ParticleData datablock names.\n\n" 244 "A random one of these datablocks is selected each time a particle is " 245 "emitted." ); 246 247 addField( "lifetimeMS", TYPEID< S32 >(), Offset(lifetimeMS, ParticleEmitterData), 248 "Lifetime of emitted particles (in milliseconds)." ); 249 250 addField("lifetimeVarianceMS", TYPEID< S32 >(), Offset(lifetimeVarianceMS, ParticleEmitterData), 251 "Variance in particle lifetime from 0 - lifetimeMS." ); 252 253 addField( "useEmitterSizes", TYPEID< bool >(), Offset(useEmitterSizes, ParticleEmitterData), 254 "@brief If true, use emitter specified sizes instead of datablock sizes.\n" 255 "Useful for Debris particle emitters that control the particle size." ); 256 257 addField( "useEmitterColors", TYPEID< bool >(), Offset(useEmitterColors, ParticleEmitterData), 258 "@brief If true, use emitter specified colors instead of datablock colors.\n\n" 259 "Useful for ShapeBase dust and WheeledVehicle wheel particle emitters that use " 260 "the current material to control particle color." ); 261 262 /// These fields added for support of user defined blend factors and optional particle sorting. 263 //@{ 264 addField( "blendStyle", TYPEID< ParticleRenderInst::BlendStyle >(), Offset(blendStyle, ParticleEmitterData), 265 "String value that controls how emitted particles blend with the scene." ); 266 267 addField( "sortParticles", TYPEID< bool >(), Offset(sortParticles, ParticleEmitterData), 268 "If true, particles are sorted furthest to nearest."); 269 270 addField( "reverseOrder", TYPEID< bool >(), Offset(reverseOrder, ParticleEmitterData), 271 "@brief If true, reverses the normal draw order of particles.\n\n" 272 "Particles are normally drawn from newest to oldest, or in Z order " 273 "(furthest first) if sortParticles is true. Setting this field to " 274 "true will reverse that order: oldest first, or nearest first if " 275 "sortParticles is true." ); 276 277 addField( "textureName", TYPEID< StringTableEntry >(), Offset(textureName, ParticleEmitterData), 278 "Optional texture to override ParticleData::textureName." ); 279 280 addField( "alignParticles", TYPEID< bool >(), Offset(alignParticles, ParticleEmitterData), 281 "If true, particles always face along the axis defined by alignDirection." ); 282 283 addProtectedField( "alignDirection", TYPEID< Point3F>(), Offset(alignDirection, ParticleEmitterData), &ParticleEmitterData::_setAlignDirection, &defaultProtectedGetFn, 284 "The direction aligned particles should face, only valid if alignParticles is true." ); 285 286 addField( "highResOnly", TYPEID< bool >(), Offset(highResOnly, ParticleEmitterData), 287 "This particle system should not use the mixed-resolution renderer. " 288 "If your particle system has large amounts of overdraw, consider " 289 "disabling this option." ); 290 291 addField( "renderReflection", TYPEID< bool >(), Offset(renderReflection, ParticleEmitterData), 292 "Controls whether particles are rendered onto reflective surfaces like water." ); 293 294 addField("glow", TYPEID< bool >(), Offset(glow, ParticleEmitterData), 295 "If true, the particles are rendered to the glow buffer as well."); 296 297 //@} 298 299 endGroup( "ParticleEmitterData" ); 300 301 Parent::initPersistFields(); 302} 303 304bool ParticleEmitterData::_setAlignDirection( void *object, const char *index, const char *data ) 305{ 306 ParticleEmitterData *p = static_cast<ParticleEmitterData*>( object ); 307 308 Con::setData( TypePoint3F, &p->alignDirection, 0, 1, &data ); 309 p->alignDirection.normalizeSafe(); 310 311 // we already set the field 312 return false; 313} 314 315//----------------------------------------------------------------------------- 316// packData 317//----------------------------------------------------------------------------- 318void ParticleEmitterData::packData(BitStream* stream) 319{ 320 Parent::packData(stream); 321 322 stream->writeInt(ejectionPeriodMS, 11); // must match limit on valid range in ParticleEmitterData::initPersistFields 323 stream->writeInt(periodVarianceMS, 11); 324 stream->writeInt((S32)(ejectionVelocity * 100), 16); 325 stream->writeInt((S32)(velocityVariance * 100), 14); 326 if( stream->writeFlag( ejectionOffset != sgDefaultEjectionOffset ) ) 327 stream->writeInt((S32)(ejectionOffset * 100), 16); 328 if( stream->writeFlag( ejectionOffsetVariance != 0.0f ) ) 329 stream->writeInt((S32)(ejectionOffsetVariance * 100), 16); 330 stream->writeRangedU32((U32)thetaMin, 0, 180); 331 stream->writeRangedU32((U32)thetaMax, 0, 180); 332 if( stream->writeFlag( phiReferenceVel != sgDefaultPhiReferenceVel ) ) 333 stream->writeRangedU32((U32)phiReferenceVel, 0, 360); 334 if( stream->writeFlag( phiVariance != sgDefaultPhiVariance ) ) 335 stream->writeRangedU32((U32)phiVariance, 0, 360); 336 337 stream->write( softnessDistance ); 338 stream->write( ambientFactor ); 339 340 stream->writeFlag(overrideAdvance); 341 stream->writeFlag(orientParticles); 342 stream->writeFlag(orientOnVelocity); 343 stream->write( lifetimeMS ); 344 stream->write( lifetimeVarianceMS ); 345 stream->writeFlag(useEmitterSizes); 346 stream->writeFlag(useEmitterColors); 347 348 stream->write(dataBlockIds.size()); 349 for (U32 i = 0; i < dataBlockIds.size(); i++) 350 stream->write(dataBlockIds[i]); 351 stream->writeFlag(sortParticles); 352 stream->writeFlag(reverseOrder); 353 if (stream->writeFlag(textureName != 0)) 354 stream->writeString(textureName); 355 356 if (stream->writeFlag(alignParticles)) 357 { 358 stream->write(alignDirection.x); 359 stream->write(alignDirection.y); 360 stream->write(alignDirection.z); 361 } 362 stream->writeFlag(highResOnly); 363 stream->writeFlag(renderReflection); 364 stream->writeFlag(glow); 365 stream->writeInt( blendStyle, 4 ); 366} 367 368//----------------------------------------------------------------------------- 369// unpackData 370//----------------------------------------------------------------------------- 371void ParticleEmitterData::unpackData(BitStream* stream) 372{ 373 Parent::unpackData(stream); 374 375 ejectionPeriodMS = stream->readInt(11); 376 periodVarianceMS = stream->readInt(11); 377 ejectionVelocity = stream->readInt(16) / 100.0f; 378 velocityVariance = stream->readInt(14) / 100.0f; 379 if( stream->readFlag() ) 380 ejectionOffset = stream->readInt(16) / 100.0f; 381 else 382 ejectionOffset = sgDefaultEjectionOffset; 383 if( stream->readFlag() ) 384 ejectionOffsetVariance = stream->readInt(16) / 100.0f; 385 else 386 ejectionOffsetVariance = 0.0f; 387 thetaMin = (F32)stream->readRangedU32(0, 180); 388 thetaMax = (F32)stream->readRangedU32(0, 180); 389 if( stream->readFlag() ) 390 phiReferenceVel = (F32)stream->readRangedU32(0, 360); 391 else 392 phiReferenceVel = sgDefaultPhiReferenceVel; 393 394 if( stream->readFlag() ) 395 phiVariance = (F32)stream->readRangedU32(0, 360); 396 else 397 phiVariance = sgDefaultPhiVariance; 398 399 stream->read( &softnessDistance ); 400 stream->read( &ambientFactor ); 401 402 overrideAdvance = stream->readFlag(); 403 orientParticles = stream->readFlag(); 404 orientOnVelocity = stream->readFlag(); 405 stream->read( &lifetimeMS ); 406 stream->read( &lifetimeVarianceMS ); 407 useEmitterSizes = stream->readFlag(); 408 useEmitterColors = stream->readFlag(); 409 410 U32 size; stream->read(&size); 411 dataBlockIds.setSize(size); 412 for (U32 i = 0; i < dataBlockIds.size(); i++) 413 stream->read(&dataBlockIds[i]); 414 sortParticles = stream->readFlag(); 415 reverseOrder = stream->readFlag(); 416 textureName = (stream->readFlag()) ? stream->readSTString() : 0; 417 418 alignParticles = stream->readFlag(); 419 if (alignParticles) 420 { 421 stream->read(&alignDirection.x); 422 stream->read(&alignDirection.y); 423 stream->read(&alignDirection.z); 424 } 425 highResOnly = stream->readFlag(); 426 renderReflection = stream->readFlag(); 427 glow = stream->readFlag(); 428 blendStyle = stream->readInt( 4 ); 429} 430 431//----------------------------------------------------------------------------- 432// onAdd 433//----------------------------------------------------------------------------- 434bool ParticleEmitterData::onAdd() 435{ 436 if( Parent::onAdd() == false ) 437 return false; 438 439// if (overrideAdvance == true) { 440// Con::errorf(ConsoleLogEntry::General, "ParticleEmitterData: Not going to work. Fix it!"); 441// return false; 442// } 443 444 // Validate the parameters... 445 // 446 if( ejectionPeriodMS < 1 ) 447 { 448 Con::warnf(ConsoleLogEntry::General, "ParticleEmitterData(%s) period < 1 ms", getName()); 449 ejectionPeriodMS = 1; 450 } 451 if( periodVarianceMS >= ejectionPeriodMS ) 452 { 453 Con::warnf(ConsoleLogEntry::General, "ParticleEmitterData(%s) periodVariance >= period", getName()); 454 periodVarianceMS = ejectionPeriodMS - 1; 455 } 456 if( ejectionVelocity < 0.0f ) 457 { 458 Con::warnf(ConsoleLogEntry::General, "ParticleEmitterData(%s) ejectionVelocity < 0.0f", getName()); 459 ejectionVelocity = 0.0f; 460 } 461 if( velocityVariance < 0.0f ) 462 { 463 Con::warnf(ConsoleLogEntry::General, "ParticleEmitterData(%s) velocityVariance < 0.0f", getName()); 464 velocityVariance = 0.0f; 465 } 466 if( velocityVariance > ejectionVelocity ) 467 { 468 Con::warnf(ConsoleLogEntry::General, "ParticleEmitterData(%s) velocityVariance > ejectionVelocity", getName()); 469 velocityVariance = ejectionVelocity; 470 } 471 if( ejectionOffset < 0.0f ) 472 { 473 Con::warnf(ConsoleLogEntry::General, "ParticleEmitterData(%s) ejectionOffset < 0", getName()); 474 ejectionOffset = 0.0f; 475 } 476 if( thetaMin < 0.0f ) 477 { 478 Con::warnf(ConsoleLogEntry::General, "ParticleEmitterData(%s) thetaMin < 0.0", getName()); 479 thetaMin = 0.0f; 480 } 481 if( thetaMax > 180.0f ) 482 { 483 Con::warnf(ConsoleLogEntry::General, "ParticleEmitterData(%s) thetaMax > 180.0", getName()); 484 thetaMax = 180.0f; 485 } 486 if( thetaMin > thetaMax ) 487 { 488 Con::warnf(ConsoleLogEntry::General, "ParticleEmitterData(%s) thetaMin > thetaMax", getName()); 489 thetaMin = thetaMax; 490 } 491 if( phiVariance < 0.0f || phiVariance > 360.0f ) 492 { 493 Con::warnf(ConsoleLogEntry::General, "ParticleEmitterData(%s) invalid phiVariance", getName()); 494 phiVariance = phiVariance < 0.0f ? 0.0f : 360.0f; 495 } 496 497 if ( softnessDistance < 0.0f ) 498 { 499 Con::warnf( ConsoleLogEntry::General, "ParticleEmitterData(%s) invalid softnessDistance", getName() ); 500 softnessDistance = 0.0f; 501 } 502 503 if (particleString == NULL && dataBlockIds.size() == 0) 504 { 505 Con::warnf(ConsoleLogEntry::General, "ParticleEmitterData(%s) no particleString, invalid datablock", getName()); 506 return false; 507 } 508 if (particleString && particleString[0] == '\0') 509 { 510 Con::warnf(ConsoleLogEntry::General, "ParticleEmitterData(%s) no particleString, invalid datablock", getName()); 511 return false; 512 } 513 if (particleString && dStrlen(particleString) > 255) 514 { 515 Con::errorf(ConsoleLogEntry::General, "ParticleEmitterData(%s) particle string too long [> 255 chars]", getName()); 516 return false; 517 } 518 519 if( lifetimeMS < 0 ) 520 { 521 Con::warnf(ConsoleLogEntry::General, "ParticleEmitterData(%s) lifetimeMS < 0.0f", getName()); 522 lifetimeMS = 0; 523 } 524 if( lifetimeVarianceMS > lifetimeMS ) 525 { 526 Con::warnf(ConsoleLogEntry::General, "ParticleEmitterData(%s) lifetimeVarianceMS >= lifetimeMS", getName()); 527 lifetimeVarianceMS = lifetimeMS; 528 } 529 530 531 // load the particle datablocks... 532 // 533 if( particleString != NULL ) 534 { 535 // particleString is once again a list of particle datablocks so it 536 // must be parsed to extract the particle references. 537 538 // First we parse particleString into a list of particle name tokens 539 Vector<char*> dataBlocks(__FILE__, __LINE__); 540 char* tokCopy = new char[dStrlen(particleString) + 1]; 541 dStrcpy(tokCopy, particleString); 542 543 char* currTok = dStrtok(tokCopy, " \t"); 544 while (currTok != NULL) 545 { 546 dataBlocks.push_back(currTok); 547 currTok = dStrtok(NULL, " \t"); 548 } 549 if (dataBlocks.size() == 0) 550 { 551 Con::warnf(ConsoleLogEntry::General, "ParticleEmitterData(%s) invalid particles string. No datablocks found", getName()); 552 delete [] tokCopy; 553 return false; 554 } 555 556 // Now we convert the particle name tokens into particle datablocks and IDs 557 particleDataBlocks.clear(); 558 dataBlockIds.clear(); 559 560 for (U32 i = 0; i < dataBlocks.size(); i++) 561 { 562 ParticleData* pData = NULL; 563 if (Sim::findObject(dataBlocks[i], pData) == false) 564 { 565 Con::warnf(ConsoleLogEntry::General, "ParticleEmitterData(%s) unable to find particle datablock: %s", getName(), dataBlocks[i]); 566 } 567 else 568 { 569 particleDataBlocks.push_back(pData); 570 dataBlockIds.push_back(pData->getId()); 571 } 572 } 573 574 // cleanup 575 delete [] tokCopy; 576 577 // check that we actually found some particle datablocks 578 if (particleDataBlocks.size() == 0) 579 { 580 Con::warnf(ConsoleLogEntry::General, "ParticleEmitterData(%s) unable to find any particle datablocks", getName()); 581 return false; 582 } 583 } 584 585 return true; 586} 587 588//----------------------------------------------------------------------------- 589// preload 590//----------------------------------------------------------------------------- 591bool ParticleEmitterData::preload(bool server, String &errorStr) 592{ 593 if( Parent::preload(server, errorStr) == false ) 594 return false; 595 596 particleDataBlocks.clear(); 597 for (U32 i = 0; i < dataBlockIds.size(); i++) 598 { 599 ParticleData* pData = NULL; 600 if (Sim::findObject(dataBlockIds[i], pData) == false) 601 Con::warnf(ConsoleLogEntry::General, "ParticleEmitterData(%s) unable to find particle datablock: %d", getName(), dataBlockIds[i]); 602 else 603 particleDataBlocks.push_back(pData); 604 } 605 606 if (!server) 607 { 608 // load emitter texture if specified 609 if (textureName && textureName[0]) 610 { 611 textureHandle = GFXTexHandle(textureName, &GFXDefaultStaticDiffuseProfile, avar("%s() - textureHandle (line %d)", __FUNCTION__, __LINE__)); 612 if (!textureHandle) 613 { 614 errorStr = String::ToString("Missing particle emitter texture: %s", textureName); 615 return false; 616 } 617 } 618 // otherwise, check that all particles refer to the same texture 619 else if (particleDataBlocks.size() > 1) 620 { 621 StringTableEntry txr_name = particleDataBlocks[0]->textureName; 622 for (S32 i = 1; i < particleDataBlocks.size(); i++) 623 { 624 // warn if particle textures are inconsistent 625 if (particleDataBlocks[i]->textureName != txr_name) 626 { 627 Con::warnf(ConsoleLogEntry::General, "ParticleEmitterData(%s) particles reference different textures.", getName()); 628 break; 629 } 630 } 631 } 632 } 633 634 // if blend-style is undefined check legacy useInvAlpha settings 635 if (blendStyle == ParticleRenderInst::BlendUndefined && particleDataBlocks.size() > 0) 636 { 637 bool useInvAlpha = particleDataBlocks[0]->useInvAlpha; 638 for (S32 i = 1; i < particleDataBlocks.size(); i++) 639 { 640 // warn if blend-style legacy useInvAlpha settings are inconsistent 641 if (particleDataBlocks[i]->useInvAlpha != useInvAlpha) 642 { 643 Con::warnf(ConsoleLogEntry::General, "ParticleEmitterData(%s) particles have inconsistent useInvAlpha settings.", getName()); 644 break; 645 } 646 } 647 blendStyle = (useInvAlpha) ? ParticleRenderInst::BlendNormal : ParticleRenderInst::BlendAdditive; 648 } 649 650 if( !server ) 651 { 652 allocPrimBuffer(); 653 } 654 655 return true; 656} 657 658//----------------------------------------------------------------------------- 659// alloc PrimitiveBuffer 660// The datablock allocates this static index buffer because it's the same 661// for all of the emitters - each particle quad uses the same index ordering 662//----------------------------------------------------------------------------- 663void ParticleEmitterData::allocPrimBuffer( S32 overrideSize ) 664{ 665 // calculate particle list size 666 AssertFatal(particleDataBlocks.size() > 0, "Error, no particles found." ); 667 U32 maxPartLife = particleDataBlocks[0]->lifetimeMS + particleDataBlocks[0]->lifetimeVarianceMS; 668 for (S32 i = 1; i < particleDataBlocks.size(); i++) 669 { 670 U32 mpl = particleDataBlocks[i]->lifetimeMS + particleDataBlocks[i]->lifetimeVarianceMS; 671 if (mpl > maxPartLife) 672 maxPartLife = mpl; 673 } 674 675 partListInitSize = maxPartLife / (ejectionPeriodMS - periodVarianceMS); 676 partListInitSize += 8; // add 8 as "fudge factor" to make sure it doesn't realloc if it goes over by 1 677 678 // if override size is specified, then the emitter overran its buffer and needs a larger allocation 679 if( overrideSize != -1 ) 680 { 681 partListInitSize = overrideSize; 682 } 683 684 // create index buffer based on that size 685 U32 indexListSize = partListInitSize * 6; // 6 indices per particle 686 U16 *indices = new U16[ indexListSize ]; 687 688 for( U32 i=0; i<partListInitSize; i++ ) 689 { 690 // this index ordering should be optimal (hopefully) for the vertex cache 691 U16 *idx = &indices[i*6]; 692 volatile U32 offset = i * 4; // set to volatile to fix VC6 Release mode compiler bug 693 idx[0] = 0 + offset; 694 idx[1] = 1 + offset; 695 idx[2] = 3 + offset; 696 idx[3] = 1 + offset; 697 idx[4] = 3 + offset; 698 idx[5] = 2 + offset; 699 } 700 701 702 U16 *ibIndices; 703 GFXBufferType bufferType = GFXBufferTypeStatic; 704 705#ifdef TORQUE_OS_XENON 706 // Because of the way the volatile buffers work on Xenon this is the only 707 // way to do this. 708 bufferType = GFXBufferTypeVolatile; 709#endif 710 primBuff.set( GFX, indexListSize, 0, bufferType ); 711 primBuff.lock( &ibIndices ); 712 dMemcpy( ibIndices, indices, indexListSize * sizeof(U16) ); 713 primBuff.unlock(); 714 715 delete [] indices; 716} 717 718 719//----------------------------------------------------------------------------- 720// ParticleEmitter 721//----------------------------------------------------------------------------- 722ParticleEmitter::ParticleEmitter() 723{ 724 mDeleteWhenEmpty = false; 725 mDeleteOnTick = false; 726 727 mInternalClock = 0; 728 mNextParticleTime = 0; 729 730 mLastPosition.set(0, 0, 0); 731 mHasLastPosition = false; 732 733 mLifetimeMS = 0; 734 mElapsedTimeMS = 0; 735 736 part_store = 0; 737 part_freelist = NULL; 738 part_list_head.next = NULL; 739 n_part_capacity = 0; 740 n_parts = 0; 741 742 mCurBuffSize = 0; 743 744 mDead = false; 745 mDataBlock = NULL; 746 747 // ParticleEmitter should be allocated on the client only. 748 mNetFlags.set( IsGhost ); 749} 750 751//----------------------------------------------------------------------------- 752// destructor 753//----------------------------------------------------------------------------- 754ParticleEmitter::~ParticleEmitter() 755{ 756 for( S32 i = 0; i < part_store.size(); i++ ) 757 { 758 delete [] part_store[i]; 759 } 760} 761 762//----------------------------------------------------------------------------- 763// onAdd 764//----------------------------------------------------------------------------- 765bool ParticleEmitter::onAdd() 766{ 767 if( !Parent::onAdd() ) 768 return false; 769 770 // add to client side mission cleanup 771 SimGroup *cleanup = dynamic_cast<SimGroup *>( Sim::findObject( "ClientMissionCleanup") ); 772 if( cleanup != NULL ) 773 { 774 cleanup->addObject( this ); 775 } 776 777 removeFromProcessList(); 778 779 F32 radius = 5.0; 780 mObjBox.minExtents = Point3F(-radius, -radius, -radius); 781 mObjBox.maxExtents = Point3F(radius, radius, radius); 782 resetWorldBox(); 783 784 return true; 785} 786 787 788//----------------------------------------------------------------------------- 789// onRemove 790//----------------------------------------------------------------------------- 791void ParticleEmitter::onRemove() 792{ 793 removeFromScene(); 794 Parent::onRemove(); 795} 796 797 798//----------------------------------------------------------------------------- 799// onNewDataBlock 800//----------------------------------------------------------------------------- 801bool ParticleEmitter::onNewDataBlock( GameBaseData *dptr, bool reload ) 802{ 803 mDataBlock = dynamic_cast<ParticleEmitterData*>( dptr ); 804 if ( !mDataBlock || !Parent::onNewDataBlock( dptr, reload ) ) 805 return false; 806 807 mLifetimeMS = mDataBlock->lifetimeMS; 808 if( mDataBlock->lifetimeVarianceMS ) 809 { 810 mLifetimeMS += S32( gRandGen.randI() % (2 * mDataBlock->lifetimeVarianceMS + 1)) - S32(mDataBlock->lifetimeVarianceMS ); 811 } 812 813 // Allocate particle structures and init the freelist. Member part_store 814 // is a Vector so that we can allocate more particles if partListInitSize 815 // turns out to be too small. 816 // 817 if (mDataBlock->partListInitSize > 0) 818 { 819 for( S32 i = 0; i < part_store.size(); i++ ) 820 { 821 delete [] part_store[i]; 822 } 823 part_store.clear(); 824 n_part_capacity = mDataBlock->partListInitSize; 825 Particle* store_block = new Particle[n_part_capacity]; 826 part_store.push_back(store_block); 827 part_freelist = store_block; 828 Particle* last_part = part_freelist; 829 Particle* part = last_part+1; 830 for( S32 i = 1; i < n_part_capacity; i++, part++, last_part++ ) 831 { 832 last_part->next = part; 833 } 834 store_block[n_part_capacity-1].next = NULL; 835 part_list_head.next = NULL; 836 n_parts = 0; 837 } 838 839 scriptOnNewDataBlock(); 840 return true; 841} 842 843//----------------------------------------------------------------------------- 844// getCollectiveColor 845//----------------------------------------------------------------------------- 846ColorF ParticleEmitter::getCollectiveColor() 847{ 848 U32 count = 0; 849 ColorF color = ColorF(0.0f, 0.0f, 0.0f); 850 851 count = n_parts; 852 for( Particle* part = part_list_head.next; part != NULL; part = part->next ) 853 { 854 color += part->color; 855 } 856 857 if(count > 0) 858 { 859 color /= F32(count); 860 } 861 862 //if(color.red == 0.0f && color.green == 0.0f && color.blue == 0.0f) 863 // color = color; 864 865 return color; 866} 867 868 869//----------------------------------------------------------------------------- 870// prepRenderImage 871//----------------------------------------------------------------------------- 872void ParticleEmitter::prepRenderImage(SceneRenderState* state) 873{ 874 if( state->isReflectPass() && !getDataBlock()->renderReflection ) 875 return; 876 877 // Never render into shadows. 878 if (state->isShadowPass()) 879 return; 880 881 PROFILE_SCOPE(ParticleEmitter_prepRenderImage); 882 883 if ( mDead || 884 n_parts == 0 || 885 part_list_head.next == NULL ) 886 return; 887 888 RenderPassManager *renderManager = state->getRenderPass(); 889 const Point3F &camPos = state->getCameraPosition(); 890 copyToVB( camPos, state->getAmbientLightColor() ); 891 892 if (!mVertBuff.isValid()) 893 return; 894 895 ParticleRenderInst *ri = renderManager->allocInst<ParticleRenderInst>(); 896 897 ri->vertBuff = &mVertBuff; 898 ri->primBuff = &getDataBlock()->primBuff; 899 ri->translucentSort = true; 900 ri->type = RenderPassManager::RIT_Particle; 901 ri->sortDistSq = getRenderWorldBox().getSqDistanceToPoint( camPos ); 902 903 // Draw the system offscreen unless the highResOnly flag is set on the datablock 904 ri->systemState = ( getDataBlock()->highResOnly ? PSS_AwaitingHighResDraw : PSS_AwaitingOffscreenDraw ); 905 906 ri->modelViewProj = renderManager->allocUniqueXform( GFX->getProjectionMatrix() * 907 GFX->getViewMatrix() * 908 GFX->getWorldMatrix() ); 909 910 // Update position on the matrix before multiplying it 911 mBBObjToWorld.setPosition(mLastPosition); 912 913 ri->bbModelViewProj = renderManager->allocUniqueXform( *ri->modelViewProj * mBBObjToWorld ); 914 915 ri->count = n_parts; 916 917 ri->blendStyle = mDataBlock->blendStyle; 918 919 ri->glow = mDataBlock->glow; 920 921 // use first particle's texture unless there is an emitter texture to override it 922 if (mDataBlock->textureHandle) 923 ri->diffuseTex = &*(mDataBlock->textureHandle); 924 else 925 ri->diffuseTex = &*(part_list_head.next->dataBlock->textureHandle); 926 927 ri->softnessDistance = mDataBlock->softnessDistance; 928 929 // Sort by texture too. 930 ri->defaultKey = ri->diffuseTex ? (uintptr_t)ri->diffuseTex : (uintptr_t)ri->vertBuff; 931 932 renderManager->addInst( ri ); 933 934} 935 936//----------------------------------------------------------------------------- 937// setSizes 938//----------------------------------------------------------------------------- 939void ParticleEmitter::setSizes( F32 *sizeList ) 940{ 941 for( S32 i=0; i<ParticleData::PDC_NUM_KEYS; i++ ) 942 { 943 sizes[i] = sizeList[i]; 944 } 945} 946 947//----------------------------------------------------------------------------- 948// setColors 949//----------------------------------------------------------------------------- 950void ParticleEmitter::setColors( ColorF *colorList ) 951{ 952 for( S32 i=0; i<ParticleData::PDC_NUM_KEYS; i++ ) 953 { 954 colors[i] = colorList[i]; 955 } 956} 957 958//----------------------------------------------------------------------------- 959// deleteWhenEmpty 960//----------------------------------------------------------------------------- 961void ParticleEmitter::deleteWhenEmpty() 962{ 963 // if the following asserts fire, there is a reasonable chance that you are trying to delete a particle emitter 964 // that has already been deleted (possibly by ClientMissionCleanup). If so, use a SimObjectPtr to the emitter and check it 965 // for null before calling this function. 966 AssertFatal(isProperlyAdded(), "ParticleEmitter must be registed before calling deleteWhenEmpty"); 967 AssertFatal(!mDead, "ParticleEmitter already deleted"); 968 AssertFatal(!isDeleted(), "ParticleEmitter already deleted"); 969 AssertFatal(!isRemoved(), "ParticleEmitter already removed"); 970 971 // this check is for non debug case, so that we don't write in to freed memory 972 bool okToDelete = !mDead && isProperlyAdded() && !isDeleted() && !isRemoved(); 973 if (okToDelete) 974 { 975 mDeleteWhenEmpty = true; 976 if( !n_parts ) 977 { 978 // We're already empty, so delete us now. 979 980 mDead = true; 981 deleteObject(); 982 } 983 else 984 AssertFatal( getSceneManager() != NULL, "ParticleEmitter not on process list and won't get ticked to death" ); 985 } 986} 987 988//----------------------------------------------------------------------------- 989// emitParticles 990//----------------------------------------------------------------------------- 991void ParticleEmitter::emitParticles(const Point3F& point, 992 const bool useLastPosition, 993 const Point3F& axis, 994 const Point3F& velocity, 995 const U32 numMilliseconds) 996{ 997 if( mDead ) return; 998 999 // lifetime over - no more particles 1000 if( mLifetimeMS > 0 && mElapsedTimeMS > mLifetimeMS ) 1001 { 1002 return; 1003 } 1004 1005 Point3F realStart; 1006 if( useLastPosition && mHasLastPosition ) 1007 realStart = mLastPosition; 1008 else 1009 realStart = point; 1010 1011 emitParticles(realStart, point, 1012 axis, 1013 velocity, 1014 numMilliseconds); 1015} 1016 1017//----------------------------------------------------------------------------- 1018// emitParticles 1019//----------------------------------------------------------------------------- 1020void ParticleEmitter::emitParticles(const Point3F& start, 1021 const Point3F& end, 1022 const Point3F& axis, 1023 const Point3F& velocity, 1024 const U32 numMilliseconds) 1025{ 1026 if( mDead ) return; 1027 1028 if( mDataBlock->particleDataBlocks.empty() ) 1029 return; 1030 1031 // lifetime over - no more particles 1032 if( mLifetimeMS > 0 && mElapsedTimeMS > mLifetimeMS ) 1033 { 1034 return; 1035 } 1036 1037 U32 currTime = 0; 1038 bool particlesAdded = false; 1039 1040 Point3F axisx; 1041 if( mFabs(axis.z) < 0.9f ) 1042 mCross(axis, Point3F(0, 0, 1), &axisx); 1043 else 1044 mCross(axis, Point3F(0, 1, 0), &axisx); 1045 axisx.normalize(); 1046 1047 if( mNextParticleTime != 0 ) 1048 { 1049 // Need to handle next particle 1050 // 1051 if( mNextParticleTime > numMilliseconds ) 1052 { 1053 // Defer to next update 1054 // (Note that this introduces a potential spatial irregularity if the owning 1055 // object is accelerating, and updating at a low frequency) 1056 // 1057 mNextParticleTime -= numMilliseconds; 1058 mInternalClock += numMilliseconds; 1059 mLastPosition = end; 1060 mHasLastPosition = true; 1061 return; 1062 } 1063 else 1064 { 1065 currTime += mNextParticleTime; 1066 mInternalClock += mNextParticleTime; 1067 // Emit particle at curr time 1068 1069 // Create particle at the correct position 1070 Point3F pos; 1071 pos.interpolate(start, end, F32(currTime) / F32(numMilliseconds)); 1072 addParticle(pos, axis, velocity, axisx); 1073 particlesAdded = true; 1074 mNextParticleTime = 0; 1075 } 1076 } 1077 1078 while( currTime < numMilliseconds ) 1079 { 1080 S32 nextTime = mDataBlock->ejectionPeriodMS; 1081 if( mDataBlock->periodVarianceMS != 0 ) 1082 { 1083 nextTime += S32(gRandGen.randI() % (2 * mDataBlock->periodVarianceMS + 1)) - 1084 S32(mDataBlock->periodVarianceMS); 1085 } 1086 AssertFatal(nextTime > 0, "Error, next particle ejection time must always be greater than 0"); 1087 1088 if( currTime + nextTime > numMilliseconds ) 1089 { 1090 mNextParticleTime = (currTime + nextTime) - numMilliseconds; 1091 mInternalClock += numMilliseconds - currTime; 1092 AssertFatal(mNextParticleTime > 0, "Error, should not have deferred this particle!"); 1093 break; 1094 } 1095 1096 currTime += nextTime; 1097 mInternalClock += nextTime; 1098 1099 // Create particle at the correct position 1100 Point3F pos; 1101 pos.interpolate(start, end, F32(currTime) / F32(numMilliseconds)); 1102 addParticle(pos, axis, velocity, axisx); 1103 particlesAdded = true; 1104 1105 // This override-advance code is restored in order to correctly adjust 1106 // animated parameters of particles allocated within the same frame 1107 // update. Note that ordering is important and this code correctly 1108 // adds particles in the same newest-to-oldest ordering of the link-list. 1109 // 1110 // NOTE: We are assuming that the just added particle is at the head of our 1111 // list. If that changes, so must this... 1112 U32 advanceMS = numMilliseconds - currTime; 1113 if (mDataBlock->overrideAdvance == false && advanceMS != 0) 1114 { 1115 Particle* last_part = part_list_head.next; 1116 if (advanceMS > last_part->totalLifetime) 1117 { 1118 part_list_head.next = last_part->next; 1119 n_parts--; 1120 last_part->next = part_freelist; 1121 part_freelist = last_part; 1122 } 1123 else 1124 { 1125 if (advanceMS != 0) 1126 { 1127 F32 t = F32(advanceMS) / 1000.0; 1128 1129 Point3F a = last_part->acc; 1130 a -= last_part->vel * last_part->dataBlock->dragCoefficient; 1131 a -= mWindVelocity * last_part->dataBlock->windCoefficient; 1132 a += Point3F(0.0f, 0.0f, -9.81f) * last_part->dataBlock->gravityCoefficient; 1133 1134 last_part->vel += a * t; 1135 last_part->pos += last_part->vel * t; 1136 1137 updateKeyData( last_part ); 1138 } 1139 } 1140 } 1141 } 1142 1143 // DMMFIX: Lame and slow... 1144 if( particlesAdded == true ) 1145 updateBBox(); 1146 1147 1148 if( n_parts > 0 && getSceneManager() == NULL ) 1149 { 1150 gClientSceneGraph->addObjectToScene(this); 1151 ClientProcessList::get()->addObject(this); 1152 } 1153 1154 mLastPosition = end; 1155 mHasLastPosition = true; 1156} 1157 1158//----------------------------------------------------------------------------- 1159// emitParticles 1160//----------------------------------------------------------------------------- 1161void ParticleEmitter::emitParticles(const Point3F& rCenter, 1162 const Point3F& rNormal, 1163 const F32 radius, 1164 const Point3F& velocity, 1165 S32 count) 1166{ 1167 if( mDead ) return; 1168 1169 // lifetime over - no more particles 1170 if( mLifetimeMS > 0 && mElapsedTimeMS > mLifetimeMS ) 1171 { 1172 return; 1173 } 1174 1175 1176 Point3F axisx, axisy; 1177 Point3F axisz = rNormal; 1178 1179 if( axisz.isZero() ) 1180 { 1181 axisz.set( 0.0, 0.0, 1.0 ); 1182 } 1183 1184 if( mFabs(axisz.z) < 0.98 ) 1185 { 1186 mCross(axisz, Point3F(0, 0, 1), &axisy); 1187 axisy.normalize(); 1188 } 1189 else 1190 { 1191 mCross(axisz, Point3F(0, 1, 0), &axisy); 1192 axisy.normalize(); 1193 } 1194 mCross(axisz, axisy, &axisx); 1195 axisx.normalize(); 1196 1197 // Should think of a better way to distribute the 1198 // particles within the hemisphere. 1199 for( S32 i = 0; i < count; i++ ) 1200 { 1201 Point3F pos = axisx * (radius * (1 - (2 * gRandGen.randF()))); 1202 pos += axisy * (radius * (1 - (2 * gRandGen.randF()))); 1203 pos += axisz * (radius * gRandGen.randF()); 1204 1205 Point3F axis = pos; 1206 axis.normalize(); 1207 pos += rCenter; 1208 1209 addParticle(pos, axis, velocity, axisz); 1210 } 1211 1212 // Set world bounding box 1213 mObjBox.minExtents = rCenter - Point3F(radius, radius, radius); 1214 mObjBox.maxExtents = rCenter + Point3F(radius, radius, radius); 1215 resetWorldBox(); 1216 1217 // Make sure we're part of the world 1218 if( n_parts > 0 && getSceneManager() == NULL ) 1219 { 1220 gClientSceneGraph->addObjectToScene(this); 1221 ClientProcessList::get()->addObject(this); 1222 } 1223 1224 mHasLastPosition = false; 1225} 1226 1227//----------------------------------------------------------------------------- 1228// updateBBox - SLOW, bad news 1229//----------------------------------------------------------------------------- 1230void ParticleEmitter::updateBBox() 1231{ 1232 Point3F minPt(1e10, 1e10, 1e10); 1233 Point3F maxPt(-1e10, -1e10, -1e10); 1234 1235 for (Particle* part = part_list_head.next; part != NULL; part = part->next) 1236 { 1237 Point3F particleSize(part->size * 0.5f, 0.0f, part->size * 0.5f); 1238 minPt.setMin( part->pos - particleSize ); 1239 maxPt.setMax( part->pos + particleSize ); 1240 } 1241 1242 mObjBox = Box3F(minPt, maxPt); 1243 MatrixF temp = getTransform(); 1244 setTransform(temp); 1245 1246 mBBObjToWorld.identity(); 1247 Point3F boxScale = mObjBox.getExtents(); 1248 boxScale.x = getMax(boxScale.x, 1.0f); 1249 boxScale.y = getMax(boxScale.y, 1.0f); 1250 boxScale.z = getMax(boxScale.z, 1.0f); 1251 mBBObjToWorld.scale(boxScale); 1252} 1253 1254//----------------------------------------------------------------------------- 1255// addParticle 1256//----------------------------------------------------------------------------- 1257void ParticleEmitter::addParticle(const Point3F& pos, 1258 const Point3F& axis, 1259 const Point3F& vel, 1260 const Point3F& axisx) 1261{ 1262 n_parts++; 1263 if (n_parts > n_part_capacity || n_parts > mDataBlock->partListInitSize) 1264 { 1265 // In an emergency we allocate additional particles in blocks of 16. 1266 // This should happen rarely. 1267 Particle* store_block = new Particle[16]; 1268 part_store.push_back(store_block); 1269 n_part_capacity += 16; 1270 for (S32 i = 0; i < 16; i++) 1271 { 1272 store_block[i].next = part_freelist; 1273 part_freelist = &store_block[i]; 1274 } 1275 mDataBlock->allocPrimBuffer(n_part_capacity); // allocate larger primitive buffer or will crash 1276 } 1277 Particle* pNew = part_freelist; 1278 part_freelist = pNew->next; 1279 pNew->next = part_list_head.next; 1280 part_list_head.next = pNew; 1281 1282 Point3F ejectionAxis = axis; 1283 F32 theta = (mDataBlock->thetaMax - mDataBlock->thetaMin) * gRandGen.randF() + 1284 mDataBlock->thetaMin; 1285 1286 F32 ref = (F32(mInternalClock) / 1000.0) * mDataBlock->phiReferenceVel; 1287 F32 phi = ref + gRandGen.randF() * mDataBlock->phiVariance; 1288 1289 // Both phi and theta are in degs. Create axis angles out of them, and create the 1290 // appropriate rotation matrix... 1291 AngAxisF thetaRot(axisx, theta * (M_PI / 180.0)); 1292 AngAxisF phiRot(axis, phi * (M_PI / 180.0)); 1293 1294 MatrixF temp(true); 1295 thetaRot.setMatrix(&temp); 1296 temp.mulP(ejectionAxis); 1297 phiRot.setMatrix(&temp); 1298 temp.mulP(ejectionAxis); 1299 1300 F32 initialVel = mDataBlock->ejectionVelocity; 1301 initialVel += (mDataBlock->velocityVariance * 2.0f * gRandGen.randF()) - mDataBlock->velocityVariance; 1302 1303 pNew->pos = pos + (ejectionAxis * (mDataBlock->ejectionOffset + mDataBlock->ejectionOffsetVariance* gRandGen.randF()) ); 1304 pNew->vel = ejectionAxis * initialVel; 1305 pNew->orientDir = ejectionAxis; 1306 pNew->acc.set(0, 0, 0); 1307 pNew->currentAge = 0; 1308 1309 // Choose a new particle datablack randomly from the list 1310 U32 dBlockIndex = gRandGen.randI() % mDataBlock->particleDataBlocks.size(); 1311 mDataBlock->particleDataBlocks[dBlockIndex]->initializeParticle(pNew, vel); 1312 updateKeyData( pNew ); 1313 1314} 1315 1316 1317//----------------------------------------------------------------------------- 1318// processTick 1319//----------------------------------------------------------------------------- 1320void ParticleEmitter::processTick(const Move*) 1321{ 1322 if( mDeleteOnTick == true ) 1323 { 1324 mDead = true; 1325 deleteObject(); 1326 } 1327} 1328 1329 1330//----------------------------------------------------------------------------- 1331// advanceTime 1332//----------------------------------------------------------------------------- 1333void ParticleEmitter::advanceTime(F32 dt) 1334{ 1335 if( dt < 0.00001 ) return; 1336 1337 Parent::advanceTime(dt); 1338 1339 if( dt > 0.5 ) dt = 0.5; 1340 1341 if( mDead ) return; 1342 1343 mElapsedTimeMS += (S32)(dt * 1000.0f); 1344 1345 U32 numMSToUpdate = (U32)(dt * 1000.0f); 1346 if( numMSToUpdate == 0 ) return; 1347 1348 // TODO: Prefetch 1349 1350 // remove dead particles 1351 Particle* last_part = &part_list_head; 1352 for (Particle* part = part_list_head.next; part != NULL; part = part->next) 1353 { 1354 part->currentAge += numMSToUpdate; 1355 if (part->currentAge > part->totalLifetime) 1356 { 1357 n_parts--; 1358 last_part->next = part->next; 1359 part->next = part_freelist; 1360 part_freelist = part; 1361 part = last_part; 1362 } 1363 else 1364 { 1365 last_part = part; 1366 } 1367 } 1368 1369 AssertFatal( n_parts >= 0, "ParticleEmitter: negative part count!" ); 1370 1371 if (n_parts < 1 && mDeleteWhenEmpty) 1372 { 1373 mDeleteOnTick = true; 1374 return; 1375 } 1376 1377 if( numMSToUpdate != 0 && n_parts > 0 ) 1378 { 1379 update( numMSToUpdate ); 1380 } 1381} 1382 1383//----------------------------------------------------------------------------- 1384// Update key related particle data 1385//----------------------------------------------------------------------------- 1386void ParticleEmitter::updateKeyData( Particle *part ) 1387{ 1388 //Ensure that our lifetime is never below 0 1389 if( part->totalLifetime < 1 ) 1390 part->totalLifetime = 1; 1391 1392 F32 t = F32(part->currentAge) / F32(part->totalLifetime); 1393 AssertFatal(t <= 1.0f, "Out out bounds filter function for particle."); 1394 1395 for( U32 i = 1; i < ParticleData::PDC_NUM_KEYS; i++ ) 1396 { 1397 if( part->dataBlock->times[i] >= t ) 1398 { 1399 F32 firstPart = t - part->dataBlock->times[i-1]; 1400 F32 total = part->dataBlock->times[i] - 1401 part->dataBlock->times[i-1]; 1402 1403 firstPart /= total; 1404 1405 if( mDataBlock->useEmitterColors ) 1406 { 1407 part->color.interpolate(colors[i-1], colors[i], firstPart); 1408 } 1409 else 1410 { 1411 part->color.interpolate(part->dataBlock->colors[i-1], 1412 part->dataBlock->colors[i], 1413 firstPart); 1414 } 1415 1416 if( mDataBlock->useEmitterSizes ) 1417 { 1418 part->size = (sizes[i-1] * (1.0 - firstPart)) + 1419 (sizes[i] * firstPart); 1420 } 1421 else 1422 { 1423 part->size = (part->dataBlock->sizes[i-1] * (1.0 - firstPart)) + 1424 (part->dataBlock->sizes[i] * firstPart); 1425 } 1426 break; 1427 1428 } 1429 } 1430} 1431 1432//----------------------------------------------------------------------------- 1433// Update particles 1434//----------------------------------------------------------------------------- 1435void ParticleEmitter::update( U32 ms ) 1436{ 1437 // TODO: Prefetch 1438 1439 for (Particle* part = part_list_head.next; part != NULL; part = part->next) 1440 { 1441 F32 t = F32(ms) / 1000.0; 1442 1443 Point3F a = part->acc; 1444 a -= part->vel * part->dataBlock->dragCoefficient; 1445 a -= mWindVelocity * part->dataBlock->windCoefficient; 1446 a += Point3F(0.0f, 0.0f, -9.81f) * part->dataBlock->gravityCoefficient; 1447 1448 part->vel += a * t; 1449 part->pos += part->vel * t; 1450 1451 updateKeyData( part ); 1452 } 1453} 1454 1455//----------------------------------------------------------------------------- 1456// Copy particles to vertex buffer 1457//----------------------------------------------------------------------------- 1458 1459// structure used for particle sorting. 1460struct SortParticle 1461{ 1462 Particle* p; 1463 F32 k; 1464}; 1465 1466// qsort callback function for particle sorting 1467S32 QSORT_CALLBACK cmpSortParticles(const void* p1, const void* p2) 1468{ 1469 const SortParticle* sp1 = (const SortParticle*)p1; 1470 const SortParticle* sp2 = (const SortParticle*)p2; 1471 1472 if (sp2->k > sp1->k) 1473 return 1; 1474 else if (sp2->k == sp1->k) 1475 return 0; 1476 else 1477 return -1; 1478} 1479 1480void ParticleEmitter::copyToVB( const Point3F &camPos, const ColorF &ambientColor ) 1481{ 1482 static Vector<SortParticle> orderedVector(__FILE__, __LINE__); 1483 1484 PROFILE_START(ParticleEmitter_copyToVB); 1485 1486 PROFILE_START(ParticleEmitter_copyToVB_Sort); 1487 // build sorted list of particles (far to near) 1488 if (mDataBlock->sortParticles) 1489 { 1490 orderedVector.clear(); 1491 1492 MatrixF modelview = GFX->getWorldMatrix(); 1493 Point3F viewvec; modelview.getRow(1, &viewvec); 1494 1495 // add each particle and a distance based sort key to orderedVector 1496 for (Particle* pp = part_list_head.next; pp != NULL; pp = pp->next) 1497 { 1498 orderedVector.increment(); 1499 orderedVector.last().p = pp; 1500 orderedVector.last().k = mDot(pp->pos, viewvec); 1501 } 1502 1503 // qsort the list into far to near ordering 1504 dQsort(orderedVector.address(), orderedVector.size(), sizeof(SortParticle), cmpSortParticles); 1505 } 1506 PROFILE_END(); 1507 1508#if defined(TORQUE_OS_XENON) 1509 // Allocate writecombined since we don't read back from this buffer (yay!) 1510 if(mVertBuff.isNull()) 1511 mVertBuff = new GFX360MemVertexBuffer(GFX, 1, getGFXVertexFormat<ParticleVertexType>(), sizeof(ParticleVertexType), GFXBufferTypeDynamic, PAGE_WRITECOMBINE); 1512 if( n_parts > mCurBuffSize ) 1513 { 1514 mCurBuffSize = n_parts; 1515 mVertBuff.resize(n_parts * 4); 1516 } 1517 1518 ParticleVertexType *buffPtr = mVertBuff.lock(); 1519#else 1520 static Vector<ParticleVertexType> tempBuff(2048); 1521 tempBuff.reserve( n_parts*4 + 64); // make sure tempBuff is big enough 1522 ParticleVertexType *buffPtr = tempBuff.address(); // use direct pointer (faster) 1523#endif 1524 1525 if (mDataBlock->orientParticles) 1526 { 1527 PROFILE_START(ParticleEmitter_copyToVB_Orient); 1528 1529 if (mDataBlock->reverseOrder) 1530 { 1531 buffPtr += 4*(n_parts-1); 1532 // do sorted-oriented particles 1533 if (mDataBlock->sortParticles) 1534 { 1535 SortParticle* partPtr = orderedVector.address(); 1536 for (U32 i = 0; i < n_parts; i++, partPtr++, buffPtr-=4 ) 1537 setupOriented(partPtr->p, camPos, ambientColor, buffPtr); 1538 } 1539 // do unsorted-oriented particles 1540 else 1541 { 1542 for (Particle* partPtr = part_list_head.next; partPtr != NULL; partPtr = partPtr->next, buffPtr-=4) 1543 setupOriented(partPtr, camPos, ambientColor, buffPtr); 1544 } 1545 } 1546 else 1547 { 1548 // do sorted-oriented particles 1549 if (mDataBlock->sortParticles) 1550 { 1551 SortParticle* partPtr = orderedVector.address(); 1552 for (U32 i = 0; i < n_parts; i++, partPtr++, buffPtr+=4 ) 1553 setupOriented(partPtr->p, camPos, ambientColor, buffPtr); 1554 } 1555 // do unsorted-oriented particles 1556 else 1557 { 1558 for (Particle* partPtr = part_list_head.next; partPtr != NULL; partPtr = partPtr->next, buffPtr+=4) 1559 setupOriented(partPtr, camPos, ambientColor, buffPtr); 1560 } 1561 } 1562 PROFILE_END(); 1563 } 1564 else if (mDataBlock->alignParticles) 1565 { 1566 PROFILE_START(ParticleEmitter_copyToVB_Aligned); 1567 1568 if (mDataBlock->reverseOrder) 1569 { 1570 buffPtr += 4*(n_parts-1); 1571 1572 // do sorted-oriented particles 1573 if (mDataBlock->sortParticles) 1574 { 1575 SortParticle* partPtr = orderedVector.address(); 1576 for (U32 i = 0; i < n_parts; i++, partPtr++, buffPtr-=4 ) 1577 setupAligned(partPtr->p, ambientColor, buffPtr); 1578 } 1579 // do unsorted-oriented particles 1580 else 1581 { 1582 Particle *partPtr = part_list_head.next; 1583 for (; partPtr != NULL; partPtr = partPtr->next, buffPtr-=4) 1584 setupAligned(partPtr, ambientColor, buffPtr); 1585 } 1586 } 1587 else 1588 { 1589 // do sorted-oriented particles 1590 if (mDataBlock->sortParticles) 1591 { 1592 SortParticle* partPtr = orderedVector.address(); 1593 for (U32 i = 0; i < n_parts; i++, partPtr++, buffPtr+=4 ) 1594 setupAligned(partPtr->p, ambientColor, buffPtr); 1595 } 1596 // do unsorted-oriented particles 1597 else 1598 { 1599 Particle *partPtr = part_list_head.next; 1600 for (; partPtr != NULL; partPtr = partPtr->next, buffPtr+=4) 1601 setupAligned(partPtr, ambientColor, buffPtr); 1602 } 1603 } 1604 PROFILE_END(); 1605 } 1606 else 1607 { 1608 PROFILE_START(ParticleEmitter_copyToVB_NonOriented); 1609 // somewhat odd ordering so that texture coordinates match the oriented 1610 // particles 1611 Point3F basePoints[4]; 1612 basePoints[0] = Point3F(-1.0, 0.0, 1.0); 1613 basePoints[1] = Point3F(-1.0, 0.0, -1.0); 1614 basePoints[2] = Point3F( 1.0, 0.0, -1.0); 1615 basePoints[3] = Point3F( 1.0, 0.0, 1.0); 1616 1617 MatrixF camView = GFX->getWorldMatrix(); 1618 camView.transpose(); // inverse - this gets the particles facing camera 1619 1620 if (mDataBlock->reverseOrder) 1621 { 1622 buffPtr += 4*(n_parts-1); 1623 // do sorted-billboard particles 1624 if (mDataBlock->sortParticles) 1625 { 1626 SortParticle *partPtr = orderedVector.address(); 1627 for( U32 i=0; i<n_parts; i++, partPtr++, buffPtr-=4 ) 1628 setupBillboard( partPtr->p, basePoints, camView, ambientColor, buffPtr ); 1629 } 1630 // do unsorted-billboard particles 1631 else 1632 { 1633 for (Particle* partPtr = part_list_head.next; partPtr != NULL; partPtr = partPtr->next, buffPtr-=4) 1634 setupBillboard( partPtr, basePoints, camView, ambientColor, buffPtr ); 1635 } 1636 } 1637 else 1638 { 1639 // do sorted-billboard particles 1640 if (mDataBlock->sortParticles) 1641 { 1642 SortParticle *partPtr = orderedVector.address(); 1643 for( U32 i=0; i<n_parts; i++, partPtr++, buffPtr+=4 ) 1644 setupBillboard( partPtr->p, basePoints, camView, ambientColor, buffPtr ); 1645 } 1646 // do unsorted-billboard particles 1647 else 1648 { 1649 for (Particle* partPtr = part_list_head.next; partPtr != NULL; partPtr = partPtr->next, buffPtr+=4) 1650 setupBillboard( partPtr, basePoints, camView, ambientColor, buffPtr ); 1651 } 1652 } 1653 1654 PROFILE_END(); 1655 } 1656 1657#if defined(TORQUE_OS_XENON) 1658 mVertBuff.unlock(); 1659#else 1660 PROFILE_START(ParticleEmitter_copyToVB_LockCopy); 1661 // create new VB if emitter size grows 1662 if( !mVertBuff || n_parts > mCurBuffSize ) 1663 { 1664 mCurBuffSize = n_parts; 1665 mVertBuff.set( GFX, n_parts * 4, GFXBufferTypeDynamic ); 1666 } 1667 // lock and copy tempBuff to video RAM 1668 ParticleVertexType *verts = mVertBuff.lock(); 1669 dMemcpy( verts, tempBuff.address(), n_parts * 4 * sizeof(ParticleVertexType) ); 1670 mVertBuff.unlock(); 1671 PROFILE_END(); 1672#endif 1673 1674 PROFILE_END(); 1675} 1676 1677//----------------------------------------------------------------------------- 1678// Set up particle for billboard style render 1679//----------------------------------------------------------------------------- 1680void ParticleEmitter::setupBillboard( Particle *part, 1681 Point3F *basePts, 1682 const MatrixF &camView, 1683 const ColorF &ambientColor, 1684 ParticleVertexType *lVerts ) 1685{ 1686 F32 width = part->size * 0.5f; 1687 F32 spinAngle = part->spinSpeed * part->currentAge * AgedSpinToRadians; 1688 1689 F32 sy, cy; 1690 mSinCos(spinAngle, sy, cy); 1691 1692 const F32 ambientLerp = mClampF( mDataBlock->ambientFactor, 0.0f, 1.0f ); 1693 ColorF partCol = mLerp( part->color, ( part->color * ambientColor ), ambientLerp ); 1694 1695 // fill four verts, use macro and unroll loop 1696 #define fillVert(){ \ 1697 lVerts->point.x = cy * basePts->x - sy * basePts->z; \ 1698 lVerts->point.y = 0.0f; \ 1699 lVerts->point.z = sy * basePts->x + cy * basePts->z; \ 1700 camView.mulV( lVerts->point ); \ 1701 lVerts->point *= width; \ 1702 lVerts->point += part->pos; \ 1703 lVerts->color = partCol; } \ 1704 1705 // Here we deal with UVs for animated particle (billboard) 1706 if (part->dataBlock->animateTexture && !part->dataBlock->animTexFrames.empty()) 1707 { 1708 S32 fm = (S32)(part->currentAge*(1.0/1000.0)*part->dataBlock->framesPerSec); 1709 U8 fm_tile = part->dataBlock->animTexFrames[fm % part->dataBlock->numFrames]; 1710 S32 uv[4]; 1711 uv[0] = fm_tile + fm_tile/part->dataBlock->animTexTiling.x; 1712 uv[1] = uv[0] + (part->dataBlock->animTexTiling.x + 1); 1713 uv[2] = uv[1] + 1; 1714 uv[3] = uv[0] + 1; 1715 1716 fillVert(); 1717 // Here and below, we copy UVs from particle datablock's current frame's UVs (billboard) 1718 lVerts->texCoord = part->dataBlock->animTexUVs[uv[0]]; 1719 ++lVerts; 1720 ++basePts; 1721 1722 fillVert(); 1723 lVerts->texCoord = part->dataBlock->animTexUVs[uv[1]]; 1724 ++lVerts; 1725 ++basePts; 1726 1727 fillVert(); 1728 lVerts->texCoord = part->dataBlock->animTexUVs[uv[2]]; 1729 ++lVerts; 1730 ++basePts; 1731 1732 fillVert(); 1733 lVerts->texCoord = part->dataBlock->animTexUVs[uv[3]]; 1734 ++lVerts; 1735 ++basePts; 1736 1737 return; 1738 } 1739 1740 fillVert(); 1741 // Here and below, we copy UVs from particle datablock's texCoords (billboard) 1742 lVerts->texCoord = part->dataBlock->texCoords[0]; 1743 ++lVerts; 1744 ++basePts; 1745 1746 fillVert(); 1747 lVerts->texCoord = part->dataBlock->texCoords[1]; 1748 ++lVerts; 1749 ++basePts; 1750 1751 fillVert(); 1752 lVerts->texCoord = part->dataBlock->texCoords[2]; 1753 ++lVerts; 1754 ++basePts; 1755 1756 fillVert(); 1757 lVerts->texCoord = part->dataBlock->texCoords[3]; 1758 ++lVerts; 1759 ++basePts; 1760} 1761 1762//----------------------------------------------------------------------------- 1763// Set up oriented particle 1764//----------------------------------------------------------------------------- 1765void ParticleEmitter::setupOriented( Particle *part, 1766 const Point3F &camPos, 1767 const ColorF &ambientColor, 1768 ParticleVertexType *lVerts ) 1769{ 1770 Point3F dir; 1771 1772 if( mDataBlock->orientOnVelocity ) 1773 { 1774 // don't render oriented particle if it has no velocity 1775 if( part->vel.magnitudeSafe() == 0.0 ) return; 1776 dir = part->vel; 1777 } 1778 else 1779 { 1780 dir = part->orientDir; 1781 } 1782 1783 Point3F dirFromCam = part->pos - camPos; 1784 Point3F crossDir; 1785 mCross( dirFromCam, dir, &crossDir ); 1786 crossDir.normalize(); 1787 dir.normalize(); 1788 1789 F32 width = part->size * 0.5f; 1790 dir *= width; 1791 crossDir *= width; 1792 Point3F start = part->pos - dir; 1793 Point3F end = part->pos + dir; 1794 1795 const F32 ambientLerp = mClampF( mDataBlock->ambientFactor, 0.0f, 1.0f ); 1796 ColorF partCol = mLerp( part->color, ( part->color * ambientColor ), ambientLerp ); 1797 1798 // Here we deal with UVs for animated particle (oriented) 1799 if (part->dataBlock->animateTexture) 1800 { 1801 // Let particle compute the UV indices for current frame 1802 S32 fm = (S32)(part->currentAge*(1.0f/1000.0f)*part->dataBlock->framesPerSec); 1803 U8 fm_tile = part->dataBlock->animTexFrames[fm % part->dataBlock->numFrames]; 1804 S32 uv[4]; 1805 uv[0] = fm_tile + fm_tile/part->dataBlock->animTexTiling.x; 1806 uv[1] = uv[0] + (part->dataBlock->animTexTiling.x + 1); 1807 uv[2] = uv[1] + 1; 1808 uv[3] = uv[0] + 1; 1809 1810 lVerts->point = start + crossDir; 1811 lVerts->color = partCol; 1812 // Here and below, we copy UVs from particle datablock's current frame's UVs (oriented) 1813 lVerts->texCoord = part->dataBlock->animTexUVs[uv[0]]; 1814 ++lVerts; 1815 1816 lVerts->point = start - crossDir; 1817 lVerts->color = partCol; 1818 lVerts->texCoord = part->dataBlock->animTexUVs[uv[1]]; 1819 ++lVerts; 1820 1821 lVerts->point = end - crossDir; 1822 lVerts->color = partCol; 1823 lVerts->texCoord = part->dataBlock->animTexUVs[uv[2]]; 1824 ++lVerts; 1825 1826 lVerts->point = end + crossDir; 1827 lVerts->color = partCol; 1828 lVerts->texCoord = part->dataBlock->animTexUVs[uv[3]]; 1829 ++lVerts; 1830 1831 return; 1832 } 1833 1834 lVerts->point = start + crossDir; 1835 lVerts->color = partCol; 1836 // Here and below, we copy UVs from particle datablock's texCoords (oriented) 1837 lVerts->texCoord = part->dataBlock->texCoords[1]; 1838 ++lVerts; 1839 1840 lVerts->point = start - crossDir; 1841 lVerts->color = partCol; 1842 lVerts->texCoord = part->dataBlock->texCoords[2]; 1843 ++lVerts; 1844 1845 lVerts->point = end - crossDir; 1846 lVerts->color = partCol; 1847 lVerts->texCoord = part->dataBlock->texCoords[3]; 1848 ++lVerts; 1849 1850 lVerts->point = end + crossDir; 1851 lVerts->color = partCol; 1852 lVerts->texCoord = part->dataBlock->texCoords[0]; 1853 ++lVerts; 1854} 1855 1856void ParticleEmitter::setupAligned( const Particle *part, 1857 const ColorF &ambientColor, 1858 ParticleVertexType *lVerts ) 1859{ 1860 // The aligned direction will always be normalized. 1861 Point3F dir = mDataBlock->alignDirection; 1862 1863 // Find a right vector for this particle. 1864 Point3F right; 1865 if (mFabs(dir.y) > mFabs(dir.z)) 1866 mCross(Point3F::UnitZ, dir, &right); 1867 else 1868 mCross(Point3F::UnitY, dir, &right); 1869 right.normalize(); 1870 1871 // If we have a spin velocity. 1872 if ( !mIsZero( part->spinSpeed ) ) 1873 { 1874 F32 spinAngle = part->spinSpeed * part->currentAge * AgedSpinToRadians; 1875 1876 // This is an inline quaternion vector rotation which 1877 // is faster that QuatF.mulP(), but generates different 1878 // results and hence cannot replace it right now. 1879 1880 F32 sin, qw; 1881 mSinCos( spinAngle * 0.5f, sin, qw ); 1882 F32 qx = dir.x * sin; 1883 F32 qy = dir.y * sin; 1884 F32 qz = dir.z * sin; 1885 1886 F32 vx = ( right.x * qw ) + ( right.z * qy ) - ( right.y * qz ); 1887 F32 vy = ( right.y * qw ) + ( right.x * qz ) - ( right.z * qx ); 1888 F32 vz = ( right.z * qw ) + ( right.y * qx ) - ( right.x * qy ); 1889 F32 vw = ( right.x * qx ) + ( right.y * qy ) + ( right.z * qz ); 1890 1891 right.x = ( qw * vx ) + ( qx * vw ) + ( qy * vz ) - ( qz * vy ); 1892 right.y = ( qw * vy ) + ( qy * vw ) + ( qz * vx ) - ( qx * vz ); 1893 right.z = ( qw * vz ) + ( qz * vw ) + ( qx * vy ) - ( qy * vx ); 1894 } 1895 1896 // Get the cross vector. 1897 Point3F cross; 1898 mCross(right, dir, &cross); 1899 1900 F32 width = part->size * 0.5f; 1901 right *= width; 1902 cross *= width; 1903 Point3F start = part->pos - right; 1904 Point3F end = part->pos + right; 1905 1906 const F32 ambientLerp = mClampF( mDataBlock->ambientFactor, 0.0f, 1.0f ); 1907 ColorF partCol = mLerp( part->color, ( part->color * ambientColor ), ambientLerp ); 1908 1909 // Here we deal with UVs for animated particle 1910 if (part->dataBlock->animateTexture) 1911 { 1912 // Let particle compute the UV indices for current frame 1913 S32 fm = (S32)(part->currentAge*(1.0f/1000.0f)*part->dataBlock->framesPerSec); 1914 U8 fm_tile = part->dataBlock->animTexFrames[fm % part->dataBlock->numFrames]; 1915 S32 uv[4]; 1916 uv[0] = fm_tile + fm_tile/part->dataBlock->animTexTiling.x; 1917 uv[1] = uv[0] + (part->dataBlock->animTexTiling.x + 1); 1918 uv[2] = uv[1] + 1; 1919 uv[3] = uv[0] + 1; 1920 1921 lVerts->point = start + cross; 1922 lVerts->color = partCol; 1923 lVerts->texCoord = part->dataBlock->animTexUVs[uv[0]]; 1924 ++lVerts; 1925 1926 lVerts->point = start - cross; 1927 lVerts->color = partCol; 1928 lVerts->texCoord = part->dataBlock->animTexUVs[uv[1]]; 1929 ++lVerts; 1930 1931 lVerts->point = end - cross; 1932 lVerts->color = partCol; 1933 lVerts->texCoord = part->dataBlock->animTexUVs[uv[2]]; 1934 ++lVerts; 1935 1936 lVerts->point = end + cross; 1937 lVerts->color = partCol; 1938 lVerts->texCoord = part->dataBlock->animTexUVs[uv[3]]; 1939 ++lVerts; 1940 } 1941 else 1942 { 1943 // Here and below, we copy UVs from particle datablock's texCoords 1944 lVerts->point = start + cross; 1945 lVerts->color = partCol; 1946 lVerts->texCoord = part->dataBlock->texCoords[0]; 1947 ++lVerts; 1948 1949 lVerts->point = start - cross; 1950 lVerts->color = partCol; 1951 lVerts->texCoord = part->dataBlock->texCoords[1]; 1952 ++lVerts; 1953 1954 lVerts->point = end - cross; 1955 lVerts->color = partCol; 1956 lVerts->texCoord = part->dataBlock->texCoords[2]; 1957 ++lVerts; 1958 1959 lVerts->point = end + cross; 1960 lVerts->color = partCol; 1961 lVerts->texCoord = part->dataBlock->texCoords[3]; 1962 ++lVerts; 1963 } 1964} 1965 1966bool ParticleEmitterData::reload() 1967{ 1968 // Clear out current particle data. 1969 1970 dataBlockIds.clear(); 1971 particleDataBlocks.clear(); 1972 1973 // Parse out particle string. 1974 1975 U32 numUnits = 0; 1976 if( particleString ) 1977 numUnits = StringUnit::getUnitCount( particleString, " \t" ); 1978 if( !particleString || !particleString[ 0 ] || !numUnits ) 1979 { 1980 Con::errorf( "ParticleEmitterData(%s) has an empty particles string.", getName() ); 1981 mReloadSignal.trigger(); 1982 return false; 1983 } 1984 1985 for( U32 i = 0; i < numUnits; ++ i ) 1986 { 1987 const char* dbName = StringUnit::getUnit( particleString, i, " \t" ); 1988 1989 ParticleData* data = NULL; 1990 if( !Sim::findObject( dbName, data ) ) 1991 { 1992 Con::errorf( ConsoleLogEntry::General, "ParticleEmitterData(%s) unable to find particle datablock: %s", getName(), dbName ); 1993 continue; 1994 } 1995 1996 particleDataBlocks.push_back( data ); 1997 dataBlockIds.push_back( data->getId() ); 1998 } 1999 2000 // Check that we actually found some particle datablocks. 2001 2002 if( particleDataBlocks.empty() ) 2003 { 2004 Con::errorf( ConsoleLogEntry::General, "ParticleEmitterData(%s) unable to find any particle datablocks", getName() ); 2005 mReloadSignal.trigger(); 2006 return false; 2007 } 2008 2009 // Trigger reload. 2010 2011 mReloadSignal.trigger(); 2012 2013 return true; 2014} 2015 2016DefineEngineMethod(ParticleEmitterData, reload, void,(),, 2017 "Reloads the ParticleData datablocks and other fields used by this emitter.\n" 2018 "@tsexample\n" 2019 "// Get the editor's current particle emitter\n" 2020 "%emitter = PE_EmitterEditor.currEmitter\n\n" 2021 "// Change a field value\n" 2022 "%emitter.setFieldValue( %propertyField, %value );\n\n" 2023 "// Reload this emitter\n" 2024 "%emitter.reload();\n" 2025 "@endtsexample\n") 2026{ 2027 object->reload(); 2028} 2029
