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.toColorI(); } \

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 href="/coding/file/pointer_8h/#pointer_8h_1aeeddce917cf130d62c370b8f216026dd">a</a> simple " "particle emitting object in the scene, use <a href="/coding/file/pointer_8h/#pointer_8h_1aeeddce917cf130d62c370b8f216026dd">a</a> <a href="/coding/class/classparticleemitternode/">ParticleEmitterNode</a> <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">instead.\n\n</a>" "This class is the <a href="/coding/file/x86unixmain_8cpp/#x86unixmain_8cpp_1a217dbf8b442f20279ea00b898af96f52">main</a> interface <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1a2732ab74fa0237854c2ba0f75f88a624">for</a> creating particles - though it is " "usually only accessed from within another object like <a href="/coding/class/classparticleemitternode/">ParticleEmitterNode</a> " "or WheeledVehicle. If using this object class(via C++) directly, be aware " "that it does< <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1a91b64995742fd30063314f12340b4b5a">b</a> >not</<a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1a91b64995742fd30063314f12340b4b5a">b</a> > track changes in <a href="/coding/file/pointer_8h/#pointer_8h_1adb82dfe18535e9a30aa97d275f82bd55">source</a> axis or velocity over the " "course of <a href="/coding/file/pointer_8h/#pointer_8h_1aeeddce917cf130d62c370b8f216026dd">a</a> single update, so emitParticles should be called at <a href="/coding/file/pointer_8h/#pointer_8h_1aeeddce917cf130d62c370b8f216026dd">a</a> fairly " "fine grain. The emitter will potentially track the last particle <a href="/coding/file/cmdgram_8cpp/#cmdgram_8cpp_1a5bafda9519252aa2d0fd038153f77dca">to</a> be " "created into the next call <a href="/coding/file/cmdgram_8cpp/#cmdgram_8cpp_1a5bafda9519252aa2d0fd038153f77dca">to</a> this function in order <a href="/coding/file/cmdgram_8cpp/#cmdgram_8cpp_1a5bafda9519252aa2d0fd038153f77dca">to</a> create <a href="/coding/file/pointer_8h/#pointer_8h_1aeeddce917cf130d62c370b8f216026dd">a</a> uniformly " "random time distribution of the <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">particles.\n\n</a>" "If the object <a href="/coding/file/cmdgram_8cpp/#cmdgram_8cpp_1a5bafda9519252aa2d0fd038153f77dca">to</a> which the emitter is attached is in motion, it should try " "<a href="/coding/file/cmdgram_8cpp/#cmdgram_8cpp_1a5bafda9519252aa2d0fd038153f77dca">to</a> ensure that <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1a2732ab74fa0237854c2ba0f75f88a624">for</a> call(<a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">n</a>+1) <a href="/coding/file/cmdgram_8cpp/#cmdgram_8cpp_1a5bafda9519252aa2d0fd038153f77dca">to</a> this function, start is equal <a href="/coding/file/cmdgram_8cpp/#cmdgram_8cpp_1a5bafda9519252aa2d0fd038153f77dca">to</a> the end " "from call(<a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">n</a>). This will ensure <a href="/coding/file/pointer_8h/#pointer_8h_1aeeddce917cf130d62c370b8f216026dd">a</a> uniform spatial <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">distribution.\n\n</a>" " @ingroup <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">FX\n</a>" " @see <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">ParticleEmitterData\n</a>" " @see <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">ParticleEmitterNode\n</a>" )
ConsoleDocClass(ParticleEmitterData , "@brief Defines particle emission properties such as ejection angle, period " "and velocity <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1a2732ab74fa0237854c2ba0f75f88a624">for</a> <a href="/coding/file/pointer_8h/#pointer_8h_1aeeddce917cf130d62c370b8f216026dd">a</a> <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">ParticleEmitter.\n\n</a>" " @<a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">tsexample\n</a>" "datablock <a href="/coding/class/classparticleemitterdata/">ParticleEmitterData</a>(GrenadeExpDustEmitter)\<a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">n</a>" "{\<a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">n</a>" " ejectionPeriodMS=1;\<a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">n</a>" " periodVarianceMS=0;\<a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">n</a>" " ejectionVelocity=15;\<a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">n</a>" " velocityVariance=0.0;\<a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">n</a>" " ejectionOffset=0.0;\<a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">n</a>" " thetaMin=85;\<a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">n</a>" " thetaMax=85;\<a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">n</a>" " thetaVariance=0;\<a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">n</a>" " phiReferenceVel=0;\<a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">n</a>" " phiVariance=360;\<a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">n</a>" " overrideAdvance=false;\<a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">n</a>" " lifetimeMS=200;\<a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">n</a>" " particles=\"GrenadeExpDust\";\n" "};\<a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">n</a>" "@<a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">endtsexample\n\n</a>" "@ingroup <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">FX\n</a>" "@see <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">ParticleEmitter\n</a>" "@see <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">ParticleData\n</a>" "@see <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">ParticleEmitterNode\n</a>" )
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.toColorI(); } \

Public Typedefs

typedef ParticleRenderInst::BlendStyle ParticleBlendStyle 

Public Variables

IRangeValidator ejectPeriodIValidator (1, 2047)
 EndImplementEnumType 
IRangeValidator periodVarianceIValidator (0, 2047)
const F32 sgDefaultEjectionOffset 
const F32 sgDefaultPhiReferenceVel 
const F32 sgDefaultPhiVariance 

Public Functions

cmpSortParticles(const void * p1, const void * p2)

ConsoleDocClass(ParticleEmitter , "@brief This object is responsible <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1a2732ab74fa0237854c2ba0f75f88a624">for</a> spawning <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">particles.\n\n</a>" "@note This class is not normally instantiated directly - <a href="/coding/file/cmdgram_8cpp/#cmdgram_8cpp_1a5bafda9519252aa2d0fd038153f77dca">to</a> place <a href="/coding/file/pointer_8h/#pointer_8h_1aeeddce917cf130d62c370b8f216026dd">a</a> simple " "particle emitting object in the scene, use <a href="/coding/file/pointer_8h/#pointer_8h_1aeeddce917cf130d62c370b8f216026dd">a</a> <a href="/coding/class/classparticleemitternode/">ParticleEmitterNode</a> <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">instead.\n\n</a>" "This class is the <a href="/coding/file/x86unixmain_8cpp/#x86unixmain_8cpp_1a217dbf8b442f20279ea00b898af96f52">main</a> interface <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1a2732ab74fa0237854c2ba0f75f88a624">for</a> creating particles - though it is " "usually only accessed from within another object like <a href="/coding/class/classparticleemitternode/">ParticleEmitterNode</a> " "or WheeledVehicle. If using this object class(via C++) directly, be aware " "that it does< <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1a91b64995742fd30063314f12340b4b5a">b</a> >not</<a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1a91b64995742fd30063314f12340b4b5a">b</a> > track changes in <a href="/coding/file/pointer_8h/#pointer_8h_1adb82dfe18535e9a30aa97d275f82bd55">source</a> axis or velocity over the " "course of <a href="/coding/file/pointer_8h/#pointer_8h_1aeeddce917cf130d62c370b8f216026dd">a</a> single update, so emitParticles should be called at <a href="/coding/file/pointer_8h/#pointer_8h_1aeeddce917cf130d62c370b8f216026dd">a</a> fairly " "fine grain. The emitter will potentially track the last particle <a href="/coding/file/cmdgram_8cpp/#cmdgram_8cpp_1a5bafda9519252aa2d0fd038153f77dca">to</a> be " "created into the next call <a href="/coding/file/cmdgram_8cpp/#cmdgram_8cpp_1a5bafda9519252aa2d0fd038153f77dca">to</a> this function in order <a href="/coding/file/cmdgram_8cpp/#cmdgram_8cpp_1a5bafda9519252aa2d0fd038153f77dca">to</a> create <a href="/coding/file/pointer_8h/#pointer_8h_1aeeddce917cf130d62c370b8f216026dd">a</a> uniformly " "random time distribution of the <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">particles.\n\n</a>" "If the object <a href="/coding/file/cmdgram_8cpp/#cmdgram_8cpp_1a5bafda9519252aa2d0fd038153f77dca">to</a> which the emitter is attached is in motion, it should try " "<a href="/coding/file/cmdgram_8cpp/#cmdgram_8cpp_1a5bafda9519252aa2d0fd038153f77dca">to</a> ensure that <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1a2732ab74fa0237854c2ba0f75f88a624">for</a> call(<a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">n</a>+1) <a href="/coding/file/cmdgram_8cpp/#cmdgram_8cpp_1a5bafda9519252aa2d0fd038153f77dca">to</a> this function, start is equal <a href="/coding/file/cmdgram_8cpp/#cmdgram_8cpp_1a5bafda9519252aa2d0fd038153f77dca">to</a> the end " "from call(<a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">n</a>). This will ensure <a href="/coding/file/pointer_8h/#pointer_8h_1aeeddce917cf130d62c370b8f216026dd">a</a> uniform spatial <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">distribution.\n\n</a>" " @ingroup <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">FX\n</a>" " @see <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">ParticleEmitterData\n</a>" " @see <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">ParticleEmitterNode\n</a>" )

ConsoleDocClass(ParticleEmitterData , "@brief Defines particle emission properties such as ejection angle, period " "and velocity <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1a2732ab74fa0237854c2ba0f75f88a624">for</a> <a href="/coding/file/pointer_8h/#pointer_8h_1aeeddce917cf130d62c370b8f216026dd">a</a> <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">ParticleEmitter.\n\n</a>" " @<a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">tsexample\n</a>" "datablock <a href="/coding/class/classparticleemitterdata/">ParticleEmitterData</a>(GrenadeExpDustEmitter)\<a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">n</a>" "{\<a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">n</a>" " ejectionPeriodMS=1;\<a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">n</a>" " periodVarianceMS=0;\<a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">n</a>" " ejectionVelocity=15;\<a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">n</a>" " velocityVariance=0.0;\<a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">n</a>" " ejectionOffset=0.0;\<a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">n</a>" " thetaMin=85;\<a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">n</a>" " thetaMax=85;\<a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">n</a>" " thetaVariance=0;\<a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">n</a>" " phiReferenceVel=0;\<a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">n</a>" " phiVariance=360;\<a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">n</a>" " overrideAdvance=false;\<a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">n</a>" " lifetimeMS=200;\<a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">n</a>" " particles=\"GrenadeExpDust\";\n" "};\<a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">n</a>" "@<a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">endtsexample\n\n</a>" "@ingroup <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">FX\n</a>" "@see <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">ParticleEmitter\n</a>" "@see <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">ParticleData\n</a>" "@see <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">ParticleEmitterNode\n</a>" )

DefineEngineMethod(ParticleEmitterData , reload , void , () )

DefineEnumType(ParticleBlendStyle )

ejectionFValidator(0. f, 655. 35f)

IMPLEMENT_CO_DATABLOCK_V1(ParticleEmitterData )

IMPLEMENT_CONOBJECT(ParticleEmitter )

ImplementEnumType(ParticleBlendStyle , "The type of visual blending style <a href="/coding/file/cmdgram_8cpp/#cmdgram_8cpp_1a5bafda9519252aa2d0fd038153f77dca">to</a> apply <a href="/coding/file/cmdgram_8cpp/#cmdgram_8cpp_1a5bafda9519252aa2d0fd038153f77dca">to</a> the <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">particles.\n</a>" "@ingroup <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">FX\n\n</a>" )

phiFValidator(0. f, 360. f)

thetaFValidator(0. f, 180. f)

velVarianceFValidator(0. f, 163. 83f)

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