particle.cpp

Engine/source/T3D/fx/particle.cpp

More...

Public Functions

ConsoleDocClass(ParticleData , "@brief Contains information <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1a2732ab74fa0237854c2ba0f75f88a624">for</a> how specific particles should look and react " "including particle colors, particle imagemap, acceleration <a href="/coding/file/pointer_8h/#pointer_8h_1a32aff7c6c4cd253fdf6563677afab5ce">value</a> <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1a2732ab74fa0237854c2ba0f75f88a624">for</a> individual " "particles and spin <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">information.\n</a>" " @<a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">tsexample\n</a>" "datablock <a href="/coding/class/classparticledata/">ParticleData</a>(GLWaterExpSmoke)\<a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">n</a>" "{\<a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">n</a>" " textureName=\"art/shapes/particles/smoke\";\n" "   dragCoefficient = 0.4;\<a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">n</a>" "   gravityCoefficient = -0.25;\<a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">n</a>" "   inheritedVelFactor = 0.025;\<a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">n</a>" "   constantAcceleration = -1.1;\<a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">n</a>" "   lifetimeMS = 1250;\<a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">n</a>" "   lifetimeVarianceMS = 0;\<a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">n</a>" "   useInvAlpha = false;\<a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">n</a>" "   spinSpeed = 1;\<a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">n</a>" "   spinRandomMin = -200.0;\<a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">n</a>" "   spinRandomMax = 200.0;\<a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">n\n</a>" "   colors[0] = \"0.1 0.1 1.0 1.0\";\<a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">n</a>" "   colors[1] = \"0.4 0.4 1.0 1.0\";\<a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">n</a>" "   colors[2] = \"0.4 0.4 1.0 0.0\";\<a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">n\n</a>" "   sizes[0] = 2.0;\<a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">n</a>" "   sizes[1] = 6.0;\<a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">n</a>" "   sizes[2] = 2.0;\<a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">n\n</a>" "   times[0] = 0.0;\<a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">n</a>" "   times[1] = 0.5;\<a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">n</a>" "   times[2] = 1.0;\<a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">n</a>" "};\<a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">n</a>" "@<a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">endtsexample\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">ParticleEmitterData\n</a>" "@see <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">ParticleEmitterNode\n</a>" )

Detailed Description

Public Variables

const F32 sgDefaultConstantAcceleration 
const F32 sgDefaultSizeBias 
const F32 sgDefaultSpinBias 
const F32 sgDefaultSpinRandomMax 
const F32 sgDefaultSpinRandomMin 
const F32 sgDefaultSpinSpeed 
const F32 sgDefaultWindCoefficient 

Public Functions

ConsoleDocClass(ParticleData , "@brief Contains information <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1a2732ab74fa0237854c2ba0f75f88a624">for</a> how specific particles should look and react " "including particle colors, particle imagemap, acceleration <a href="/coding/file/pointer_8h/#pointer_8h_1a32aff7c6c4cd253fdf6563677afab5ce">value</a> <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1a2732ab74fa0237854c2ba0f75f88a624">for</a> individual " "particles and spin <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">information.\n</a>" " @<a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">tsexample\n</a>" "datablock <a href="/coding/class/classparticledata/">ParticleData</a>(GLWaterExpSmoke)\<a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">n</a>" "{\<a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">n</a>" " textureName=\"art/shapes/particles/smoke\";\n" "   dragCoefficient = 0.4;\<a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">n</a>" "   gravityCoefficient = -0.25;\<a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">n</a>" "   inheritedVelFactor = 0.025;\<a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">n</a>" "   constantAcceleration = -1.1;\<a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">n</a>" "   lifetimeMS = 1250;\<a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">n</a>" "   lifetimeVarianceMS = 0;\<a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">n</a>" "   useInvAlpha = false;\<a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">n</a>" "   spinSpeed = 1;\<a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">n</a>" "   spinRandomMin = -200.0;\<a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">n</a>" "   spinRandomMax = 200.0;\<a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">n\n</a>" "   colors[0] = \"0.1 0.1 1.0 1.0\";\<a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">n</a>" "   colors[1] = \"0.4 0.4 1.0 1.0\";\<a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">n</a>" "   colors[2] = \"0.4 0.4 1.0 0.0\";\<a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">n\n</a>" "   sizes[0] = 2.0;\<a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">n</a>" "   sizes[1] = 6.0;\<a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">n</a>" "   sizes[2] = 2.0;\<a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">n\n</a>" "   times[0] = 0.0;\<a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">n</a>" "   times[1] = 0.5;\<a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">n</a>" "   times[2] = 1.0;\<a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">n</a>" "};\<a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">n</a>" "@<a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">endtsexample\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">ParticleEmitterData\n</a>" "@see <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">ParticleEmitterNode\n</a>" )

DefineEngineMethod(ParticleData , reload , void , () )

dragCoefFValidator(0. f, 5. f)

gravCoefFValidator(-10. f, 10. f)

IMPLEMENT_CO_DATABLOCK_V1(ParticleData )

spinRandFValidator(-1000. f, 1000. f)

  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 "particle.h"
 30#include "console/consoleTypes.h"
 31#include "console/typeValidators.h"
 32#include "core/stream/bitStream.h"
 33#include "math/mRandom.h"
 34#include "math/mathIO.h"
 35#include "console/engineAPI.h"
 36
 37IMPLEMENT_CO_DATABLOCK_V1( ParticleData );
 38
 39ConsoleDocClass( ParticleData,
 40   "@brief Contains information for how specific particles should look and react "
 41   "including particle colors, particle imagemap, acceleration value for individual "
 42   "particles and spin information.\n"
 43
 44   "@tsexample\n"
 45   "datablock ParticleData( GLWaterExpSmoke )\n"
 46   "{\n"
 47   "   textureName = \"art/shapes/particles/smoke\";\n"
 48   "   dragCoefficient = 0.4;\n"
 49   "   gravityCoefficient = -0.25;\n"
 50   "   inheritedVelFactor = 0.025;\n"
 51   "   constantAcceleration = -1.1;\n"
 52   "   lifetimeMS = 1250;\n"
 53   "   lifetimeVarianceMS = 0;\n"
 54   "   useInvAlpha = false;\n"
 55   "   spinSpeed = 1;\n"
 56   "   spinRandomMin = -200.0;\n"
 57   "   spinRandomMax = 200.0;\n\n"
 58   "   colors[0] = \"0.1 0.1 1.0 1.0\";\n"
 59   "   colors[1] = \"0.4 0.4 1.0 1.0\";\n"
 60   "   colors[2] = \"0.4 0.4 1.0 0.0\";\n\n"
 61   "   sizes[0] = 2.0;\n"
 62   "   sizes[1] = 6.0;\n"
 63   "   sizes[2] = 2.0;\n\n"
 64   "   times[0] = 0.0;\n"
 65   "   times[1] = 0.5;\n"
 66   "   times[2] = 1.0;\n"
 67   "};\n"
 68   "@endtsexample\n"
 69
 70   "@ingroup FX\n"
 71   "@see ParticleEmitter\n"
 72   "@see ParticleEmitterData\n"
 73   "@see ParticleEmitterNode\n"
 74);
 75
 76static const F32 sgDefaultWindCoefficient = 0.0f;
 77static const F32 sgDefaultConstantAcceleration = 0.f;
 78static const F32 sgDefaultSpinSpeed = 1.f;
 79static const F32 sgDefaultSpinRandomMin = 0.f;
 80static const F32 sgDefaultSpinRandomMax = 0.f;
 81
 82static const F32 sgDefaultSpinBias = 1.0f;
 83static const F32 sgDefaultSizeBias = 1.0f;
 84
 85//-----------------------------------------------------------------------------
 86// Constructor
 87//-----------------------------------------------------------------------------
 88ParticleData::ParticleData()
 89{
 90   dragCoefficient      = 0.0f;
 91   windCoefficient      = sgDefaultWindCoefficient;
 92   gravityCoefficient   = 0.0f;
 93   inheritedVelFactor   = 0.0f;
 94   constantAcceleration = sgDefaultConstantAcceleration;
 95   lifetimeMS           = 1000;
 96   lifetimeVarianceMS   = 0;
 97   spinSpeed            = sgDefaultSpinSpeed;
 98   spinRandomMin        = sgDefaultSpinRandomMin;
 99   spinRandomMax        = sgDefaultSpinRandomMax;
100   useInvAlpha          = false;
101   animateTexture       = false;
102
103   numFrames            = 1;
104   framesPerSec         = numFrames;
105
106   S32 i;
107   for( i=0; i<PDC_NUM_KEYS; i++ )
108   {
109      colors[i].set( 1.0, 1.0, 1.0, 1.0 );
110      sizes[i] = 1.0;
111   }
112
113   times[0] = 0.0f;
114   times[1] = 1.0f;
115   for (i = 2; i < PDC_NUM_KEYS; i++)
116     times[i] = -1.0f;
117
118   texCoords[0].set(0.0,0.0);   // texture coords at 4 corners
119   texCoords[1].set(0.0,1.0);   // of particle quad
120   texCoords[2].set(1.0,1.0);   // (defaults to entire particle)
121   texCoords[3].set(1.0,0.0);
122   animTexTiling.set(0,0);      // tiling dimensions 
123   animTexFramesString = NULL;  // string of animation frame indices
124   animTexUVs = NULL;           // array of tile vertex UVs
125   textureName = NULL;          // texture filename
126   textureHandle = NULL;        // loaded texture handle
127   textureExtName = NULL;
128   textureExtHandle = NULL;
129   constrain_pos = false;
130   start_angle = 0.0f;
131   angle_variance = 0.0f;
132   sizeBias = sgDefaultSizeBias;
133   spinBias = sgDefaultSpinBias;
134   randomizeSpinDir = false;
135}
136
137//-----------------------------------------------------------------------------
138// Destructor
139//-----------------------------------------------------------------------------
140
141
142FRangeValidator dragCoefFValidator(0.f, 5.f);
143FRangeValidator gravCoefFValidator(-10.f, 10.f);
144FRangeValidator spinRandFValidator(-1000.f, 1000.f);
145
146//-----------------------------------------------------------------------------
147// initPersistFields
148//-----------------------------------------------------------------------------
149void ParticleData::initPersistFields()
150{
151   addFieldV( "dragCoefficient", TYPEID< F32 >(), Offset(dragCoefficient, ParticleData), &dragCoefFValidator,
152      "Particle physics drag amount." );
153   addField( "windCoefficient", TYPEID< F32 >(), Offset(windCoefficient, ParticleData),
154      "Strength of wind on the particles." );
155   addFieldV( "gravityCoefficient", TYPEID< F32 >(), Offset(gravityCoefficient, ParticleData), &gravCoefFValidator,
156      "Strength of gravity on the particles." );
157   addFieldV( "inheritedVelFactor", TYPEID< F32 >(), Offset(inheritedVelFactor, ParticleData), &CommonValidators::NormalizedFloat,
158      "Amount of emitter velocity to add to particle initial velocity." );
159   addField( "constantAcceleration", TYPEID< F32 >(), Offset(constantAcceleration, ParticleData),
160      "Constant acceleration to apply to this particle." );
161   addField( "lifetimeMS", TYPEID< S32 >(), Offset(lifetimeMS, ParticleData),
162      "Time in milliseconds before this particle is destroyed." );
163   addField( "lifetimeVarianceMS", TYPEID< S32 >(), Offset(lifetimeVarianceMS, ParticleData),
164      "Variance in lifetime of particle, from 0 - lifetimeMS." );
165   addField( "spinSpeed", TYPEID< F32 >(), Offset(spinSpeed, ParticleData),
166      "Speed at which to spin the particle." );
167   addFieldV( "spinRandomMin", TYPEID< F32 >(), Offset(spinRandomMin, ParticleData), &spinRandFValidator,
168      "Minimum allowed spin speed of this particle, between -1000 and spinRandomMax." );
169   addFieldV( "spinRandomMax", TYPEID< F32 >(), Offset(spinRandomMax, ParticleData), &spinRandFValidator,
170      "Maximum allowed spin speed of this particle, between spinRandomMin and 1000." );
171   addField( "useInvAlpha", TYPEID< bool >(), Offset(useInvAlpha, ParticleData),
172      "@brief Controls how particles blend with the scene.\n\n"
173      "If true, particles blend like ParticleBlendStyle NORMAL, if false, "
174      "blend like ParticleBlendStyle ADDITIVE.\n"
175      "@note If ParticleEmitterData::blendStyle is set, it will override this value." );
176   addField( "animateTexture", TYPEID< bool >(), Offset(animateTexture, ParticleData),
177      "If true, allow the particle texture to be an animated sprite." );
178   addField( "framesPerSec", TYPEID< S32 >(), Offset(framesPerSec, ParticleData),
179      "If animateTexture is true, this defines the frames per second of the "
180      "sprite animation." );
181
182   addField( "textureCoords", TYPEID< Point2F >(), Offset(texCoords, ParticleData),  4,
183      "@brief 4 element array defining the UV coords into textureName to use "
184      "for this particle.\n\n"
185      "Coords should be set for the first tile only when using animTexTiling; "
186      "coordinates for other tiles will be calculated automatically. \"0 0\" is "
187      "top left and \"1 1\" is bottom right." );
188   addField( "animTexTiling", TYPEID< Point2I >(), Offset(animTexTiling, ParticleData),
189      "@brief The number of frames, in rows and columns stored in textureName "
190      "(when animateTexture is true).\n\n"
191      "A maximum of 256 frames can be stored in a single texture when using "
192      "animTexTiling. Value should be \"NumColumns NumRows\", for example \"4 4\"." );
193   addField( "animTexFrames", TYPEID< StringTableEntry >(), Offset(animTexFramesString,ParticleData),
194      "@brief A list of frames and/or frame ranges to use for particle "
195      "animation if animateTexture is true.\n\n"
196      "Each frame token must be separated by whitespace. A frame token must be "
197      "a positive integer frame number or a range of frame numbers separated "
198      "with a '-'. The range separator, '-', cannot have any whitspace around "
199      "it.\n\n"
200      "Ranges can be specified to move through the frames in reverse as well "
201      "as forward (eg. 19-14). Frame numbers exceeding the number of tiles will "
202      "wrap.\n"
203      "@tsexample\n"
204      "animTexFrames = \"0-16 20 19 18 17 31-21\";\n"
205      "@endtsexample\n" );
206
207   addField( "textureName", TYPEID< StringTableEntry >(), Offset(textureName, ParticleData),
208      "Texture file to use for this particle." );
209   addField( "animTexName", TYPEID< StringTableEntry >(), Offset(textureName, ParticleData),
210      "@brief Texture file to use for this particle if animateTexture is true.\n\n"
211      "Deprecated. Use textureName instead." );
212
213   // Interpolation variables
214   addField( "colors", TYPEID< LinearColorF >(), Offset(colors, ParticleData), PDC_NUM_KEYS,
215      "@brief Particle RGBA color keyframe values.\n\n"
216      "The particle color will linearly interpolate between the color/time keys "
217      "over the lifetime of the particle." );
218   addProtectedField( "sizes", TYPEID< F32 >(), Offset(sizes, ParticleData), &protectedSetSizes, 
219      &defaultProtectedGetFn, PDC_NUM_KEYS,
220      "@brief Particle size keyframe values.\n\n"
221      "The particle size will linearly interpolate between the size/time keys "
222      "over the lifetime of the particle." );
223   addProtectedField( "times", TYPEID< F32 >(), Offset(times, ParticleData), &protectedSetTimes, 
224      &defaultProtectedGetFn, PDC_NUM_KEYS,
225      "@brief Time keys used with the colors and sizes keyframes.\n\n"
226      "Values are from 0.0 (particle creation) to 1.0 (end of lifespace)." );
227
228   addGroup("AFX"); 
229   addField("textureExtName",       TypeFilename, Offset(textureExtName,     ParticleData));
230   addField("constrainPos",         TypeBool,     Offset(constrain_pos,      ParticleData));
231   addField("angle",                TypeF32,      Offset(start_angle,        ParticleData));
232   addField("angleVariance",        TypeF32,      Offset(angle_variance,     ParticleData));
233   addField("sizeBias",             TypeF32,      Offset(sizeBias,           ParticleData));
234   addField("spinBias",             TypeF32,      Offset(spinBias,           ParticleData));
235   addField("randomizeSpinDir",     TypeBool,     Offset(randomizeSpinDir,   ParticleData));
236   endGroup("AFX"); 
237   Parent::initPersistFields();
238}
239
240//-----------------------------------------------------------------------------
241// Pack data
242//-----------------------------------------------------------------------------
243void ParticleData::packData(BitStream* stream)
244{
245   Parent::packData(stream);
246
247   stream->writeFloat(dragCoefficient / 5, 10);
248   if( stream->writeFlag(windCoefficient != sgDefaultWindCoefficient ) )
249      stream->write(windCoefficient);
250   if (stream->writeFlag(gravityCoefficient != 0.0f))
251     stream->writeSignedFloat(gravityCoefficient / 10, 12); 
252   stream->writeFloat(inheritedVelFactor, 9);
253   if( stream->writeFlag( constantAcceleration != sgDefaultConstantAcceleration ) )
254      stream->write(constantAcceleration);
255
256   stream->write( lifetimeMS );
257   stream->write( lifetimeVarianceMS );
258
259   if( stream->writeFlag( spinSpeed != sgDefaultSpinSpeed ) )
260      stream->write(spinSpeed);
261   if(stream->writeFlag(spinRandomMin != sgDefaultSpinRandomMin || spinRandomMax != sgDefaultSpinRandomMax))
262   {
263      stream->writeInt((S32)(spinRandomMin + 1000), 11);
264      stream->writeInt((S32)(spinRandomMax + 1000), 11);
265   }
266   if(stream->writeFlag(spinBias != sgDefaultSpinBias))
267      stream->write(spinBias);
268   stream->writeFlag(randomizeSpinDir);
269   stream->writeFlag(useInvAlpha);
270
271   S32 i, count;
272
273   // see how many frames there are:
274   for(count = 0; count < ParticleData::PDC_NUM_KEYS-1; count++)
275      if(times[count] >= 1)
276         break;
277
278   count++;
279
280   // An extra bit is needed for 8 keys.
281   stream->writeInt(count-1, 3);
282
283   for( i=0; i<count; i++ )
284   {
285      stream->writeFloat( colors[i].red, 7);
286      stream->writeFloat( colors[i].green, 7);
287      stream->writeFloat( colors[i].blue, 7);
288      stream->writeFloat( colors[i].alpha, 7);
289      // AFX bits raised from 14 to 16 to allow larger sizes
290      stream->writeFloat( sizes[i]/<a href="/coding/file/particle_8h/#particle_8h_1acba9461aeb4fafe9946d9468cb99f0c5">MaxParticleSize</a>, 16);
291      stream->writeFloat( times[i], 8);
292   }
293
294   if (stream->writeFlag(textureName && textureName[0]))
295     stream->writeString(textureName);
296   for (i = 0; i < 4; i++)
297      mathWrite(*stream, texCoords[i]);
298   if (stream->writeFlag(animateTexture))
299   {
300      if (stream->writeFlag(animTexFramesString && animTexFramesString[0]))
301      {
302         stream->writeString(animTexFramesString);
303      }
304      mathWrite(*stream, animTexTiling);
305      stream->writeInt(framesPerSec, 8);
306   }
307   if (stream->writeFlag(textureExtName && textureExtName[0]))
308     stream->writeString(textureExtName);
309   stream->writeFlag(constrain_pos);
310   stream->writeFloat(start_angle/360.0f, 11);
311   stream->writeFloat(angle_variance/180.0f, 10);
312   if(stream->writeFlag(sizeBias != sgDefaultSizeBias))
313      stream->write(sizeBias);
314}
315
316//-----------------------------------------------------------------------------
317// Unpack data
318//-----------------------------------------------------------------------------
319void ParticleData::unpackData(BitStream* stream)
320{
321   Parent::unpackData(stream);
322
323   dragCoefficient = stream->readFloat(10) * 5;
324   if(stream->readFlag())
325      stream->read(&windCoefficient);
326   else
327      windCoefficient = sgDefaultWindCoefficient;
328   if (stream->readFlag()) 
329     gravityCoefficient = stream->readSignedFloat(12)*10; 
330   else 
331     gravityCoefficient = 0.0f; 
332   inheritedVelFactor = stream->readFloat(9);
333   if(stream->readFlag())
334      stream->read(&constantAcceleration);
335   else
336      constantAcceleration = sgDefaultConstantAcceleration;
337
338   stream->read( &lifetimeMS );
339   stream->read( &lifetimeVarianceMS );
340
341   if(stream->readFlag())
342      stream->read(&spinSpeed);
343   else
344      spinSpeed = sgDefaultSpinSpeed;
345
346   if(stream->readFlag())
347   {
348      spinRandomMin = (F32)(stream->readInt(11) - 1000);
349      spinRandomMax = (F32)(stream->readInt(11) - 1000);
350   }
351   else
352   {
353      spinRandomMin = sgDefaultSpinRandomMin;
354      spinRandomMax = sgDefaultSpinRandomMax;
355   }
356
357   if(stream->readFlag())
358      stream->read(&spinBias);
359   else
360      spinBias = sgDefaultSpinBias;
361   randomizeSpinDir = stream->readFlag();
362   useInvAlpha = stream->readFlag();
363
364   S32 i;
365   // An extra bit is needed for 8 keys.
366   S32 count = stream->readInt(3) + 1;
367   for(i = 0;i < count; i++)
368   {
369      colors[i].red = stream->readFloat(7);
370      colors[i].green = stream->readFloat(7);
371      colors[i].blue = stream->readFloat(7);
372      colors[i].alpha = stream->readFloat(7);
373      // AFX bits raised from 14 to 16 to allow larger sizes
374      sizes[i] = stream->readFloat(16) * MaxParticleSize;
375      times[i] = stream->readFloat(8);
376   }
377   textureName = (stream->readFlag()) ? stream->readSTString() : 0;
378   for (i = 0; i < 4; i++)
379      mathRead(*stream, &texCoords[i]);
380   
381   animateTexture = stream->readFlag();
382   if (animateTexture)
383   {
384     animTexFramesString = (stream->readFlag()) ? stream->readSTString() : 0;
385     mathRead(*stream, &animTexTiling);
386     framesPerSec = stream->readInt(8);
387   }
388   textureExtName = (stream->readFlag()) ? stream->readSTString() : 0;
389   constrain_pos = stream->readFlag();
390   start_angle = 360.0f*stream->readFloat(11);
391   angle_variance = 180.0f*stream->readFloat(10);
392   if(stream->readFlag())
393      stream->read(&sizeBias);
394   else
395      sizeBias = sgDefaultSizeBias;
396}
397
398bool ParticleData::protectedSetSizes( void *object, const char *index, const char *data) 
399{
400   ParticleData *pData = static_cast<ParticleData*>( object );
401   F32 val = dAtof(data);
402   U32 i;
403
404   if (!index)
405      return (val >= 0.f && val <= MaxParticleSize);
406   else
407      i = dAtoui(index);
408
409   pData->sizes[i] = mClampF( val, 0.f, MaxParticleSize );
410
411   return false;
412}
413
414bool ParticleData::protectedSetTimes( void *object, const char *index, const char *data) 
415{
416   ParticleData *pData = static_cast<ParticleData*>( object );
417   F32 val = dAtof(data);
418   U32 i;
419
420   if (!index)
421      return (val >= 0.f && val <= 1.f);
422   else
423      i = dAtoui(index);
424
425   pData->times[i] = mClampF( val, 0.f, 1.f );
426
427   return false;
428}
429
430//-----------------------------------------------------------------------------
431// onAdd
432//-----------------------------------------------------------------------------
433bool ParticleData::onAdd()
434{
435   if (Parent::onAdd() == false)
436      return false;
437
438   if (dragCoefficient < 0.0) {
439      Con::warnf(ConsoleLogEntry::General, "ParticleData(%s) drag coeff less than 0", getName());
440      dragCoefficient = 0.0f;
441   }
442   if (lifetimeMS < 1) {
443      Con::warnf(ConsoleLogEntry::General, "ParticleData(%s) lifetime < 1 ms", getName());
444      lifetimeMS = 1;
445   }
446   if (lifetimeVarianceMS >= lifetimeMS) {
447      Con::warnf(ConsoleLogEntry::General, "ParticleData(%s) lifetimeVariance >= lifetime", getName());
448      lifetimeVarianceMS = lifetimeMS - 1;
449   }
450   if (spinSpeed > 1000.f || spinSpeed < -1000.f) {
451      Con::warnf(ConsoleLogEntry::General, "ParticleData(%s) spinSpeed invalid", getName());
452      return false;
453   }
454   if (spinRandomMin > 1000.f || spinRandomMin < -1000.f) {
455      Con::warnf(ConsoleLogEntry::General, "ParticleData(%s) spinRandomMin invalid", getName());
456      spinRandomMin = -360.0;
457      return false;
458   }
459   if (spinRandomMin > spinRandomMax) {
460      Con::warnf(ConsoleLogEntry::General, "ParticleData(%s) spinRandomMin greater than spinRandomMax", getName());
461      spinRandomMin = spinRandomMax - (spinRandomMin - spinRandomMax );
462      return false;
463   }
464   if (spinRandomMax > 1000.f || spinRandomMax < -1000.f) {
465      Con::warnf(ConsoleLogEntry::General, "ParticleData(%s) spinRandomMax invalid", getName());
466      spinRandomMax = 360.0;
467      return false;
468   }
469   if (framesPerSec > 255)
470   {
471      Con::warnf(ConsoleLogEntry::General, "ParticleData(%s) framesPerSec > 255, too high", getName());
472      framesPerSec = 255;
473      return false;
474   }
475
476   times[0] = 0.0f;
477   for (U32 i = 1; i < PDC_NUM_KEYS; i++) 
478   {
479     if (times[i] < 0.0f)
480       break;
481     if (times[i] < times[i-1]) 
482     {
483       Con::warnf(ConsoleLogEntry::General, "ParticleData(%s) times[%d] < times[%d]", getName(), i, i-1);
484       times[i] = times[i-1];
485     }
486   }
487
488   times[0] = 0.0f;
489
490   U32 last_idx = 0;
491   for (U32 i = 1; i < PDC_NUM_KEYS; i++)
492   {
493     if (times[i] < 0.0f)
494       break;
495     else
496       last_idx = i;
497   }
498
499   for (U32 i = last_idx+1; i < PDC_NUM_KEYS; i++) 
500   {
501      times[i] = times[last_idx];
502      colors[i] = colors[last_idx];
503      sizes[i] = sizes[last_idx];
504   }
505
506   // Here we validate parameters
507   if (animateTexture) 
508   {
509     // Tiling dimensions must be positive and non-zero
510     if (animTexTiling.x <= 0 || animTexTiling.y <= 0)
511     {
512       Con::warnf(ConsoleLogEntry::General, 
513                  "ParticleData(%s) bad value(s) for animTexTiling [%d or %d <= 0], invalid datablock", 
514                  animTexTiling.x, animTexTiling.y, getName());
515       return false;
516     }
517
518     // Indices must fit into a byte so these are also bad
519     if (animTexTiling.x * animTexTiling.y > 256)
520     {
521       Con::warnf(ConsoleLogEntry::General, 
522                  "ParticleData(%s) bad values for animTexTiling [%d*%d > %d], invalid datablock", 
523                  animTexTiling.x, animTexTiling.y, 256, getName());
524       return false;
525     }
526
527     // A list of frames is required
528     if (!animTexFramesString || !animTexFramesString[0]) 
529     {
530       Con::warnf(ConsoleLogEntry::General, "ParticleData(%s) no animTexFrames, invalid datablock", getName());
531       return false;
532     }
533
534     // The frame list cannot be too long.
535     if (animTexFramesString && dStrlen(animTexFramesString) > 255) 
536     {
537       Con::errorf(ConsoleLogEntry::General, "ParticleData(%s) animTexFrames string too long [> 255 chars]", getName());
538       return false;
539     }
540   }
541
542   start_angle = mFmod(start_angle, 360.0f);
543   if (start_angle < 0.0f)
544     start_angle += 360.0f;
545   angle_variance = mClampF(angle_variance, -180.0f, 180.0f);
546   return true;
547}
548
549//-----------------------------------------------------------------------------
550// preload
551//-----------------------------------------------------------------------------
552bool ParticleData::preload(bool server, String &errorStr)
553{
554   if (Parent::preload(server, errorStr) == false)
555      return false;
556
557   bool error = false;
558   if(!server)
559   {
560      // Here we attempt to load the particle's texture if specified. An undefined
561      // texture is *not* an error since the emitter may provide one.
562      if (textureName && textureName[0])
563      {
564        textureHandle = GFXTexHandle(textureName, &GFXStaticTextureSRGBProfile, avar("%s() - textureHandle (line %d)", __FUNCTION__, __LINE__));
565        if (!textureHandle)
566        {
567          errorStr = String::ToString("Missing particle texture: %s", textureName);
568          error = true;
569        }
570      }
571      if (textureExtName && textureExtName[0])
572      {
573         textureExtHandle = GFXTexHandle(textureExtName, &GFXStaticTextureSRGBProfile, avar("%s() - textureExtHandle (line %d)", __FUNCTION__, __LINE__));
574         if (!textureExtHandle)
575         {
576            errorStr = String::ToString("Missing particle texture: %s", textureName);
577            error = true;
578         }
579      }
580
581      if (animateTexture) 
582      {
583        // Here we parse animTexFramesString into byte-size frame numbers in animTexFrames.
584        // Each frame token must be separated by whitespace.
585        // A frame token must be a positive integer frame number or a range of frame numbers
586        // separated with a '-'. 
587        // The range separator, '-', cannot have any whitspace around it.
588        // Ranges can be specified to move through the frames in reverse as well as forward.
589        // Frame numbers exceeding the number of tiles will wrap.
590        //   example:
591        //     "0-16 20 19 18 17 31-21"
592
593        S32 n_tiles = animTexTiling.x * animTexTiling.y;
594        AssertFatal(n_tiles > 0 && n_tiles <= 256, "Error, bad animTexTiling setting." );
595
596        animTexFrames.clear();
597
598        dsize_t tokLen = dStrlen(animTexFramesString) + 1;
599        char* tokCopy = new char[tokLen];
600        dStrcpy(tokCopy, animTexFramesString, tokLen);
601
602        char* currTok = dStrtok(tokCopy, " \t");
603        while (currTok != NULL) 
604        {
605          char* minus = dStrchr(currTok, '-');
606          if (minus)
607          { 
608            // add a range of frames
609            *minus = '\0';
610            S32 range_a = dAtoi(currTok);
611            S32 range_b = dAtoi(minus+1);
612            if (range_b < range_a)
613            {
614              // reverse frame range
615              for (S32 i = range_a; i >= range_b; i--)
616                animTexFrames.push_back((U8)(i % n_tiles));
617            }
618            else
619            {
620              // forward frame range
621              for (S32 i = range_a; i <= range_b; i++)
622                animTexFrames.push_back((U8)(i % n_tiles));
623            }
624          }
625          else
626          {
627            // add one frame
628            animTexFrames.push_back((U8)(dAtoi(currTok) % n_tiles));
629          }
630          currTok = dStrtok(NULL, " \t");
631        }
632
633        // Here we pre-calculate the UVs for each frame tile, which are
634        // tiled inside the UV region specified by texCoords. Since the
635        // UVs are calculated using bilinear interpolation, the texCoords
636        // region does *not* have to be an axis-aligned rectangle.
637
638        if (animTexUVs)
639          delete [] animTexUVs;
640
641        animTexUVs = new Point2F[(animTexTiling.x+1)*(animTexTiling.y+1)];
642
643        // interpolate points on the left and right edge of the uv quadrangle
644        Point2F lf_pt = texCoords[0];
645        Point2F rt_pt = texCoords[3];
646
647        // per-row delta for left and right interpolated points
648        Point2F lf_d = (texCoords[1] - texCoords[0])/(F32)animTexTiling.y;
649        Point2F rt_d = (texCoords[2] - texCoords[3])/(F32)animTexTiling.y;
650
651        S32 idx = 0;
652        for (S32 yy = 0; yy <= animTexTiling.y; yy++)
653        {
654          Point2F p = lf_pt;
655          Point2F dp = (rt_pt - lf_pt)/(F32)animTexTiling.x;
656          for (S32 xx = 0; xx <= animTexTiling.x; xx++)
657          {
658            animTexUVs[idx++] = p;
659            p += dp;
660          }
661          lf_pt += lf_d;
662          rt_pt += rt_d;
663        }
664
665        // cleanup
666        delete [] tokCopy;
667        numFrames = animTexFrames.size();
668      }
669   }
670
671   return !error;
672}
673
674//-----------------------------------------------------------------------------
675// Initialize particle
676//-----------------------------------------------------------------------------
677void ParticleData::initializeParticle(Particle* init, const Point3F& inheritVelocity)
678{
679   init->dataBlock = this;
680
681   // Calculate the constant accleration...
682   init->vel += inheritVelocity * inheritedVelFactor;
683   init->acc  = init->vel * constantAcceleration;
684
685   // Calculate this instance's lifetime...
686   init->totalLifetime = lifetimeMS;
687   if (lifetimeVarianceMS != 0)
688      init->totalLifetime += S32(gRandGen.randI() % (2 * lifetimeVarianceMS + 1)) - S32(lifetimeVarianceMS);
689
690   // assign spin amount
691   init->spinSpeed = spinSpeed * gRandGen.randF( spinRandomMin, spinRandomMax );
692   // apply spin bias
693   init->spinSpeed *= spinBias;
694   // randomize spin direction
695   if (randomizeSpinDir && (gRandGen.randI( 0, 1 ) == 1))
696     init->spinSpeed = -init->spinSpeed;
697}
698
699bool ParticleData::reload(char errorBuffer[256])
700{
701   bool error = false;
702   if (textureName && textureName[0])
703   {
704        textureHandle = GFXTexHandle(textureName, &GFXStaticTextureSRGBProfile, avar("%s() - textureHandle (line %d)", __FUNCTION__, __LINE__));
705        if (!textureHandle)
706        {
707            dSprintf(errorBuffer, 256, "Missing particle texture: %s", textureName);
708            error = true;
709        }
710   }
711   /*
712   numFrames = 0;
713   for( S32 i=0; i<PDC_MAX_TEX; i++ )
714   {
715      if( textureNameList[i] && textureNameList[i][0] )
716      {
717         textureList[i] = TextureHandle( textureNameList[i], MeshTexture );
718         if (!textureList[i].getName())
719         {
720            dSprintf(errorBuffer, 256, "Missing particle texture: %s", textureNameList[i]);
721            error = true;
722         }
723         numFrames++;
724      }
725   }
726   */
727   return !error;
728}
729
730DefineEngineMethod(ParticleData, reload, void, (),,
731   "Reloads this particle.\n"
732   "@tsexample\n"
733   "// Get the editor's current particle\n"
734   "%particle = PE_ParticleEditor.currParticle\n\n"
735   "// Change a particle value\n"
736   "%particle.setFieldValue( %propertyField, %value );\n\n"
737   "// Reload it\n"
738   "%particle.reload();\n"
739   "@endtsexample\n" )
740{
741   char errorBuffer[256];
742   object->reload(errorBuffer);
743}
744//#define TRACK_PARTICLE_DATA_CLONES
745
746#ifdef TRACK_PARTICLE_DATA_CLONES
747static int particle_data_clones = 0;
748#endif
749
750ParticleData::ParticleData(const ParticleData& other, bool temp_clone) : SimDataBlock(other, temp_clone)
751{
752#ifdef TRACK_PARTICLE_DATA_CLONES
753   particle_data_clones++;
754   if (particle_data_clones == 1)
755     Con::errorf("ParticleData -- Clones are on the loose!");
756#endif
757
758  dragCoefficient = other.dragCoefficient;
759  windCoefficient = other.windCoefficient;
760  gravityCoefficient = other.gravityCoefficient;
761  inheritedVelFactor = other.inheritedVelFactor;
762  constantAcceleration = other.constantAcceleration;
763  lifetimeMS = other.lifetimeMS;
764  lifetimeVarianceMS = other.lifetimeVarianceMS;
765  spinSpeed = other.spinSpeed;
766  spinRandomMin = other.spinRandomMin;
767  spinRandomMax = other.spinRandomMax;
768  useInvAlpha = other.useInvAlpha;
769  animateTexture = other.animateTexture;
770  numFrames = other.numFrames; // -- calc from other fields
771  framesPerSec = other.framesPerSec;
772  dMemcpy( colors, other.colors, sizeof( colors ) );
773  dMemcpy( sizes, other.sizes, sizeof( sizes ) );
774  dMemcpy( times, other.times, sizeof( times ) );
775  animTexUVs = other.animTexUVs; // -- calc from other fields
776  dMemcpy( texCoords, other.texCoords, sizeof( texCoords ) );
777  animTexTiling = other.animTexTiling;
778  animTexFramesString = other.animTexFramesString;
779  animTexFrames = other.animTexFrames; // -- parsed from animTexFramesString
780  textureName = other.textureName;
781  textureHandle = other.textureHandle;
782  spinBias = other.spinBias;
783  randomizeSpinDir = other.randomizeSpinDir;
784  textureExtName = other.textureExtName;
785  textureExtHandle = other.textureExtHandle;
786  constrain_pos = other.constrain_pos;
787  start_angle = other.start_angle;
788  angle_variance = other.angle_variance;
789  sizeBias = other.sizeBias;
790}
791
792ParticleData::~ParticleData()
793{
794   if (animTexUVs)
795   {
796      delete [] animTexUVs;
797   }
798
799  if (!isTempClone())
800    return;
801
802#ifdef TRACK_PARTICLE_DATA_CLONES
803  if (particle_data_clones > 0)
804  {
805    particle_data_clones--;
806    if (particle_data_clones == 0)
807      Con::errorf("ParticleData -- Clones eliminated!");
808  }
809  else
810    Con::errorf("ParticleData -- Too many clones deleted!");
811#endif
812}
813
814void ParticleData::onPerformSubstitutions() 
815{ 
816  char errorBuffer[256];
817  reload(errorBuffer);
818}
819