particle.cpp 30 KB

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