Torque3D Documentation / _generateds / particleEmitter.cpp

particleEmitter.cpp

Engine/source/T3D/fx/particleEmitter.cpp

More...

Classes:

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 Functions

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>" )
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>" )

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