babylon.particleSystem.ts 77 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679168016811682168316841685168616871688168916901691169216931694169516961697169816991700170117021703170417051706170717081709171017111712171317141715171617171718171917201721172217231724172517261727172817291730173117321733173417351736173717381739174017411742174317441745174617471748
  1. module BABYLON {
  2. /**
  3. * This represents a particle system in Babylon.
  4. * Particles are often small sprites used to simulate hard-to-reproduce phenomena like fire, smoke, water, or abstract visual effects like magic glitter and faery dust.
  5. * Particles can take different shapes while emitted like box, sphere, cone or you can write your custom function.
  6. * @example https://doc.babylonjs.com/babylon101/particles
  7. */
  8. export class ParticleSystem extends BaseParticleSystem implements IDisposable, IAnimatable, IParticleSystem {
  9. /**
  10. * This function can be defined to provide custom update for active particles.
  11. * This function will be called instead of regular update (age, position, color, etc.).
  12. * Do not forget that this function will be called on every frame so try to keep it simple and fast :)
  13. */
  14. public updateFunction: (particles: Particle[]) => void;
  15. private _emitterWorldMatrix: Matrix;
  16. /**
  17. * This function can be defined to specify initial direction for every new particle.
  18. * It by default use the emitterType defined function
  19. */
  20. public startDirectionFunction: (worldMatrix: Matrix, directionToUpdate: Vector3, particle: Particle) => void;
  21. /**
  22. * This function can be defined to specify initial position for every new particle.
  23. * It by default use the emitterType defined function
  24. */
  25. public startPositionFunction: (worldMatrix: Matrix, positionToUpdate: Vector3, particle: Particle) => void;
  26. /**
  27. * An event triggered when the system is disposed
  28. */
  29. public onDisposeObservable = new Observable<ParticleSystem>();
  30. private _onDisposeObserver: Nullable<Observer<ParticleSystem>>;
  31. /**
  32. * Sets a callback that will be triggered when the system is disposed
  33. */
  34. public set onDispose(callback: () => void) {
  35. if (this._onDisposeObserver) {
  36. this.onDisposeObservable.remove(this._onDisposeObserver);
  37. }
  38. this._onDisposeObserver = this.onDisposeObservable.add(callback);
  39. }
  40. /**
  41. * Get hosting scene
  42. * @returns the scene
  43. */
  44. public getScene(): Scene {
  45. return this._scene;
  46. }
  47. private _particles = new Array<Particle>();
  48. private _epsilon: number;
  49. private _capacity: number;
  50. private _stockParticles = new Array<Particle>();
  51. private _newPartsExcess = 0;
  52. private _vertexData: Float32Array;
  53. private _vertexBuffer: Nullable<Buffer>;
  54. private _vertexBuffers: { [key: string]: VertexBuffer } = {};
  55. private _spriteBuffer: Nullable<Buffer>;
  56. private _indexBuffer: Nullable<WebGLBuffer>;
  57. private _effect: Effect;
  58. private _customEffect: Nullable<Effect>;
  59. private _cachedDefines: string;
  60. private _scaledColorStep = new Color4(0, 0, 0, 0);
  61. private _colorDiff = new Color4(0, 0, 0, 0);
  62. private _scaledDirection = Vector3.Zero();
  63. private _scaledGravity = Vector3.Zero();
  64. private _currentRenderId = -1;
  65. private _alive: boolean;
  66. private _useInstancing = false;
  67. private _started = false;
  68. private _stopped = false;
  69. private _actualFrame = 0;
  70. private _scaledUpdateSpeed: number;
  71. private _vertexBufferSize: number;
  72. // end of sheet animation
  73. // Sub-emitters
  74. /**
  75. * this is the Sub-emitters templates that will be used to generate particle system when the particle dies, this property is used by the root particle system only.
  76. */
  77. public subEmitters: ParticleSystem[];
  78. /**
  79. * The current active Sub-systems, this property is used by the root particle system only.
  80. */
  81. public activeSubSystems: Array<ParticleSystem>;
  82. private _rootParticleSystem: ParticleSystem;
  83. //end of Sub-emitter
  84. /**
  85. * Gets the current list of active particles
  86. */
  87. public get particles(): Particle[] {
  88. return this._particles;
  89. }
  90. /**
  91. * Returns the string "ParticleSystem"
  92. * @returns a string containing the class name
  93. */
  94. public getClassName(): string {
  95. return "ParticleSystem";
  96. }
  97. /**
  98. * Instantiates a particle system.
  99. * Particles are often small sprites used to simulate hard-to-reproduce phenomena like fire, smoke, water, or abstract visual effects like magic glitter and faery dust.
  100. * @param name The name of the particle system
  101. * @param capacity The max number of particles alive at the same time
  102. * @param scene The scene the particle system belongs to
  103. * @param customEffect a custom effect used to change the way particles are rendered by default
  104. * @param isAnimationSheetEnabled Must be true if using a spritesheet to animate the particles texture
  105. * @param epsilon Offset used to render the particles
  106. */
  107. constructor(name: string, capacity: number, scene: Scene, customEffect: Nullable<Effect> = null, isAnimationSheetEnabled: boolean = false, epsilon: number = 0.01) {
  108. super(name);
  109. this._capacity = capacity;
  110. this._epsilon = epsilon;
  111. this._isAnimationSheetEnabled = isAnimationSheetEnabled;
  112. this._scene = scene || Engine.LastCreatedScene;
  113. // Setup the default processing configuration to the scene.
  114. this._attachImageProcessingConfiguration(null);
  115. this._customEffect = customEffect;
  116. this._scene.particleSystems.push(this);
  117. this._useInstancing = this._scene.getEngine().getCaps().instancedArrays;
  118. this._createIndexBuffer();
  119. this._createVertexBuffers();
  120. // Default emitter type
  121. this.particleEmitterType = new BoxParticleEmitter();
  122. this.updateFunction = (particles: Particle[]): void => {
  123. let noiseTextureData: Nullable<Uint8Array> = null;
  124. let noiseTextureSize: Nullable<ISize> = null;
  125. if (this.noiseTexture) { // We need to get texture data back to CPU
  126. noiseTextureData = <Nullable<Uint8Array>>(this.noiseTexture.readPixels());
  127. noiseTextureSize = this.noiseTexture.getSize();
  128. }
  129. for (var index = 0; index < particles.length; index++) {
  130. var particle = particles[index];
  131. particle.age += this._scaledUpdateSpeed;
  132. if (particle.age >= particle.lifeTime) { // Recycle by swapping with last particle
  133. this._emitFromParticle(particle);
  134. this.recycleParticle(particle);
  135. index--;
  136. continue;
  137. }
  138. else {
  139. let ratio = particle.age / particle.lifeTime;
  140. // Color
  141. if (this._colorGradients && this._colorGradients.length > 0) {
  142. Tools.GetCurrentGradient(ratio, this._colorGradients, (currentGradient, nextGradient, scale) => {
  143. if (currentGradient !== particle._currentColorGradient) {
  144. particle._currentColor1.copyFrom(particle._currentColor2);
  145. (<ColorGradient>nextGradient).getColorToRef(particle._currentColor2);
  146. particle._currentColorGradient = (<ColorGradient>currentGradient);
  147. }
  148. Color4.LerpToRef(particle._currentColor1, particle._currentColor2, scale, particle.color);
  149. });
  150. }
  151. else {
  152. particle.colorStep.scaleToRef(this._scaledUpdateSpeed, this._scaledColorStep);
  153. particle.color.addInPlace(this._scaledColorStep);
  154. if (particle.color.a < 0) {
  155. particle.color.a = 0;
  156. }
  157. }
  158. // Angular speed
  159. if (this._angularSpeedGradients && this._angularSpeedGradients.length > 0) {
  160. Tools.GetCurrentGradient(ratio, this._angularSpeedGradients, (currentGradient, nextGradient, scale) => {
  161. if (currentGradient !== particle._currentAngularSpeedGradient) {
  162. particle._currentAngularSpeed1 = particle._currentAngularSpeed2;
  163. particle._currentAngularSpeed2 = (<FactorGradient>nextGradient).getFactor();
  164. particle._currentAngularSpeedGradient = (<FactorGradient>currentGradient);
  165. }
  166. particle.angularSpeed = Scalar.Lerp(particle._currentAngularSpeed1, particle._currentAngularSpeed2, scale);
  167. });
  168. }
  169. particle.angle += particle.angularSpeed * this._scaledUpdateSpeed;
  170. // Direction
  171. let directionScale = this._scaledUpdateSpeed;
  172. /// Velocity
  173. if (this._velocityGradients && this._velocityGradients.length > 0) {
  174. Tools.GetCurrentGradient(ratio, this._velocityGradients, (currentGradient, nextGradient, scale) => {
  175. if (currentGradient !== particle._currentVelocityGradient) {
  176. particle._currentVelocity1 = particle._currentVelocity2;
  177. particle._currentVelocity2 = (<FactorGradient>nextGradient).getFactor();
  178. particle._currentVelocityGradient = (<FactorGradient>currentGradient);
  179. }
  180. directionScale *= Scalar.Lerp(particle._currentVelocity1, particle._currentVelocity2, scale);
  181. });
  182. }
  183. particle.direction.scaleToRef(directionScale, this._scaledDirection);
  184. /// Limit velocity
  185. if (this._limitVelocityGradients && this._limitVelocityGradients.length > 0) {
  186. Tools.GetCurrentGradient(ratio, this._limitVelocityGradients, (currentGradient, nextGradient, scale) => {
  187. if (currentGradient !== particle._currentLimitVelocityGradient) {
  188. particle._currentLimitVelocity1 = particle._currentLimitVelocity2;
  189. particle._currentLimitVelocity2 = (<FactorGradient>nextGradient).getFactor();
  190. particle._currentLimitVelocityGradient = (<FactorGradient>currentGradient);
  191. }
  192. let limitVelocity = Scalar.Lerp(particle._currentLimitVelocity1, particle._currentLimitVelocity2, scale);
  193. let currentVelocity = particle.direction.length();
  194. if (currentVelocity > limitVelocity) {
  195. particle.direction.scaleInPlace(this.limitVelocityDamping);
  196. }
  197. });
  198. }
  199. /// Drag
  200. if (this._dragGradients && this._dragGradients.length > 0) {
  201. Tools.GetCurrentGradient(ratio, this._dragGradients, (currentGradient, nextGradient, scale) => {
  202. if (currentGradient !== particle._currentDragGradient) {
  203. particle._currentDrag1 = particle._currentDrag2;
  204. particle._currentDrag2 = (<FactorGradient>nextGradient).getFactor();
  205. particle._currentDragGradient = (<FactorGradient>currentGradient);
  206. }
  207. let drag = Scalar.Lerp(particle._currentDrag1, particle._currentDrag2, scale);
  208. this._scaledDirection.scaleInPlace(drag);
  209. });
  210. }
  211. particle.position.addInPlace(this._scaledDirection);
  212. // Noise
  213. if (noiseTextureData && noiseTextureSize) {
  214. let localPosition = Tmp.Vector3[0];
  215. let emitterPosition = Tmp.Vector3[1];
  216. this._emitterWorldMatrix.getTranslationToRef(emitterPosition);
  217. particle.position.subtractToRef(emitterPosition, localPosition);
  218. let fetchedColorR = this._fetchR(localPosition.y, localPosition.z, noiseTextureSize.width, noiseTextureSize.height, noiseTextureData);
  219. let fetchedColorG = this._fetchR(localPosition.x + 0.33, localPosition.z + 0.33, noiseTextureSize.width, noiseTextureSize.height, noiseTextureData);
  220. let fetchedColorB = this._fetchR(localPosition.x - 0.33, localPosition.y - 0.33, noiseTextureSize.width, noiseTextureSize.height, noiseTextureData);
  221. let force = Tmp.Vector3[0];
  222. let scaledForce = Tmp.Vector3[1];
  223. force.copyFromFloats((2 * fetchedColorR - 1) * this.noiseStrength.x, (2 * fetchedColorG - 1) * this.noiseStrength.y, (2 * fetchedColorB - 1) * this.noiseStrength.z);
  224. force.scaleToRef(this._scaledUpdateSpeed, scaledForce);
  225. particle.direction.addInPlace(scaledForce);
  226. }
  227. // Gravity
  228. this.gravity.scaleToRef(this._scaledUpdateSpeed, this._scaledGravity);
  229. particle.direction.addInPlace(this._scaledGravity);
  230. // Size
  231. if (this._sizeGradients && this._sizeGradients.length > 0) {
  232. Tools.GetCurrentGradient(ratio, this._sizeGradients, (currentGradient, nextGradient, scale) => {
  233. if (currentGradient !== particle._currentSizeGradient) {
  234. particle._currentSize1 = particle._currentSize2;
  235. particle._currentSize2 = (<FactorGradient>nextGradient).getFactor();
  236. particle._currentSizeGradient = (<FactorGradient>currentGradient);
  237. }
  238. particle.size = Scalar.Lerp(particle._currentSize1, particle._currentSize2, scale);
  239. });
  240. }
  241. if (this._isAnimationSheetEnabled) {
  242. particle.updateCellIndex();
  243. }
  244. }
  245. }
  246. }
  247. }
  248. private _addFactorGradient(factorGradients: FactorGradient[], gradient: number, factor: number, factor2?: number) {
  249. let newGradient = new FactorGradient();
  250. newGradient.gradient = gradient;
  251. newGradient.factor1 = factor;
  252. newGradient.factor2 = factor2;
  253. factorGradients.push(newGradient);
  254. factorGradients.sort((a, b) => {
  255. if (a.gradient < b.gradient) {
  256. return -1;
  257. } else if (a.gradient > b.gradient) {
  258. return 1;
  259. }
  260. return 0;
  261. });
  262. }
  263. private _removeFactorGradient(factorGradients: Nullable<FactorGradient[]>, gradient: number) {
  264. if (!factorGradients) {
  265. return;
  266. }
  267. let index = 0;
  268. for (var factorGradient of factorGradients) {
  269. if (factorGradient.gradient === gradient) {
  270. factorGradients.splice(index, 1);
  271. break;
  272. }
  273. index++;
  274. }
  275. }
  276. /**
  277. * Adds a new life time gradient
  278. * @param gradient defines the gradient to use (between 0 and 1)
  279. * @param factor defines the life time factor to affect to the specified gradient
  280. * @param factor2 defines an additional factor used to define a range ([factor, factor2]) with main value to pick the final value from
  281. * @returns the current particle system
  282. */
  283. public addLifeTimeGradient(gradient: number, factor: number, factor2?: number): IParticleSystem {
  284. if (!this._lifeTimeGradients) {
  285. this._lifeTimeGradients = [];
  286. }
  287. this._addFactorGradient(this._lifeTimeGradients, gradient, factor, factor2);
  288. return this;
  289. }
  290. /**
  291. * Remove a specific life time gradient
  292. * @param gradient defines the gradient to remove
  293. * @returns the current particle system
  294. */
  295. public removeLifeTimeGradient(gradient: number): IParticleSystem {
  296. this._removeFactorGradient(this._lifeTimeGradients, gradient);
  297. return this;
  298. }
  299. /**
  300. * Adds a new size gradient
  301. * @param gradient defines the gradient to use (between 0 and 1)
  302. * @param factor defines the size factor to affect to the specified gradient
  303. * @param factor2 defines an additional factor used to define a range ([factor, factor2]) with main value to pick the final value from
  304. * @returns the current particle system
  305. */
  306. public addSizeGradient(gradient: number, factor: number, factor2?: number): IParticleSystem {
  307. if (!this._sizeGradients) {
  308. this._sizeGradients = [];
  309. }
  310. this._addFactorGradient(this._sizeGradients, gradient, factor, factor2);
  311. return this;
  312. }
  313. /**
  314. * Remove a specific size gradient
  315. * @param gradient defines the gradient to remove
  316. * @returns the current particle system
  317. */
  318. public removeSizeGradient(gradient: number): IParticleSystem {
  319. this._removeFactorGradient(this._sizeGradients, gradient);
  320. return this;
  321. }
  322. /**
  323. * Adds a new angular speed gradient
  324. * @param gradient defines the gradient to use (between 0 and 1)
  325. * @param factor defines the angular speed to affect to the specified gradient
  326. * @param factor2 defines an additional factor used to define a range ([factor, factor2]) with main value to pick the final value from
  327. * @returns the current particle system
  328. */
  329. public addAngularSpeedGradient(gradient: number, factor: number, factor2?: number): IParticleSystem {
  330. if (!this._angularSpeedGradients) {
  331. this._angularSpeedGradients = [];
  332. }
  333. this._addFactorGradient(this._angularSpeedGradients, gradient, factor, factor2);
  334. return this;
  335. }
  336. /**
  337. * Remove a specific angular speed gradient
  338. * @param gradient defines the gradient to remove
  339. * @returns the current particle system
  340. */
  341. public removeAngularSpeedGradient(gradient: number): IParticleSystem {
  342. this._removeFactorGradient(this._angularSpeedGradients, gradient);
  343. return this;
  344. }
  345. /**
  346. * Adds a new velocity gradient
  347. * @param gradient defines the gradient to use (between 0 and 1)
  348. * @param factor defines the velocity to affect to the specified gradient
  349. * @param factor2 defines an additional factor used to define a range ([factor, factor2]) with main value to pick the final value from
  350. * @returns the current particle system
  351. */
  352. public addVelocityGradient(gradient: number, factor: number, factor2?: number): IParticleSystem {
  353. if (!this._velocityGradients) {
  354. this._velocityGradients = [];
  355. }
  356. this._addFactorGradient(this._velocityGradients, gradient, factor, factor2);
  357. return this;
  358. }
  359. /**
  360. * Remove a specific velocity gradient
  361. * @param gradient defines the gradient to remove
  362. * @returns the current particle system
  363. */
  364. public removeVelocityGradient(gradient: number): IParticleSystem {
  365. this._removeFactorGradient(this._velocityGradients, gradient);
  366. return this;
  367. }
  368. /**
  369. * Adds a new limit velocity gradient
  370. * @param gradient defines the gradient to use (between 0 and 1)
  371. * @param factor defines the limit velocity value to affect to the specified gradient
  372. * @param factor2 defines an additional factor used to define a range ([factor, factor2]) with main value to pick the final value from
  373. * @returns the current particle system
  374. */
  375. public addLimitVelocityGradient(gradient: number, factor: number, factor2?: number): IParticleSystem {
  376. if (!this._limitVelocityGradients) {
  377. this._limitVelocityGradients = [];
  378. }
  379. this._addFactorGradient(this._limitVelocityGradients, gradient, factor, factor2);
  380. return this;
  381. }
  382. /**
  383. * Remove a specific limit velocity gradient
  384. * @param gradient defines the gradient to remove
  385. * @returns the current particle system
  386. */
  387. public removeLimitVelocityGradient(gradient: number): IParticleSystem {
  388. this._removeFactorGradient(this._limitVelocityGradients, gradient);
  389. return this;
  390. }
  391. /**
  392. * Adds a new drag gradient
  393. * @param gradient defines the gradient to use (between 0 and 1)
  394. * @param factor defines the drag value to affect to the specified gradient
  395. * @param factor2 defines an additional factor used to define a range ([factor, factor2]) with main value to pick the final value from
  396. * @returns the current particle system
  397. */
  398. public addDragGradient(gradient: number, factor: number, factor2?: number): IParticleSystem {
  399. if (!this._dragGradients) {
  400. this._dragGradients = [];
  401. }
  402. this._addFactorGradient(this._dragGradients, gradient, factor, factor2);
  403. return this;
  404. }
  405. /**
  406. * Remove a specific drag gradient
  407. * @param gradient defines the gradient to remove
  408. * @returns the current particle system
  409. */
  410. public removeDragGradient(gradient: number): IParticleSystem {
  411. this._removeFactorGradient(this._dragGradients, gradient);
  412. return this;
  413. }
  414. /**
  415. * Adds a new color gradient
  416. * @param gradient defines the gradient to use (between 0 and 1)
  417. * @param color defines the color to affect to the specified gradient
  418. * @param color2 defines an additional color used to define a range ([color, color2]) with main color to pick the final color from
  419. */
  420. public addColorGradient(gradient: number, color: Color4, color2?: Color4): IParticleSystem {
  421. if (!this._colorGradients) {
  422. this._colorGradients = [];
  423. }
  424. let colorGradient = new ColorGradient();
  425. colorGradient.gradient = gradient;
  426. colorGradient.color1 = color;
  427. colorGradient.color2 = color2;
  428. this._colorGradients.push(colorGradient);
  429. this._colorGradients.sort((a, b) => {
  430. if (a.gradient < b.gradient) {
  431. return -1;
  432. } else if (a.gradient > b.gradient) {
  433. return 1;
  434. }
  435. return 0;
  436. });
  437. return this;
  438. }
  439. /**
  440. * Remove a specific color gradient
  441. * @param gradient defines the gradient to remove
  442. */
  443. public removeColorGradient(gradient: number): IParticleSystem {
  444. if (!this._colorGradients) {
  445. return this;
  446. }
  447. let index = 0;
  448. for (var colorGradient of this._colorGradients) {
  449. if (colorGradient.gradient === gradient) {
  450. this._colorGradients.splice(index, 1);
  451. break;
  452. }
  453. index++;
  454. }
  455. return this;
  456. }
  457. private _fetchR(u: number, v: number, width: number, height: number, pixels: Uint8Array): number {
  458. u = Math.abs(u) * 0.5 + 0.5;
  459. v = Math.abs(v) * 0.5 + 0.5;
  460. let wrappedU = ((u * width) % width) | 0;
  461. let wrappedV = ((v * height) % height) | 0;
  462. let position = (wrappedU + wrappedV * width) * 4;
  463. return pixels[position] / 255;
  464. }
  465. protected _reset() {
  466. this._resetEffect();
  467. }
  468. private _resetEffect() {
  469. if (this._vertexBuffer) {
  470. this._vertexBuffer.dispose();
  471. this._vertexBuffer = null;
  472. }
  473. if (this._spriteBuffer) {
  474. this._spriteBuffer.dispose();
  475. this._spriteBuffer = null;
  476. }
  477. this._createVertexBuffers();
  478. }
  479. private _createVertexBuffers() {
  480. this._vertexBufferSize = this._useInstancing ? 10 : 12;
  481. if (this._isAnimationSheetEnabled) {
  482. this._vertexBufferSize += 1;
  483. }
  484. if (!this._isBillboardBased) {
  485. this._vertexBufferSize += 3;
  486. }
  487. let engine = this._scene.getEngine();
  488. this._vertexData = new Float32Array(this._capacity * this._vertexBufferSize * (this._useInstancing ? 1 : 4));
  489. this._vertexBuffer = new Buffer(engine, this._vertexData, true, this._vertexBufferSize);
  490. let dataOffset = 0;
  491. var positions = this._vertexBuffer.createVertexBuffer(VertexBuffer.PositionKind, dataOffset, 3, this._vertexBufferSize, this._useInstancing);
  492. this._vertexBuffers[VertexBuffer.PositionKind] = positions;
  493. dataOffset += 3;
  494. var colors = this._vertexBuffer.createVertexBuffer(VertexBuffer.ColorKind, dataOffset, 4, this._vertexBufferSize, this._useInstancing);
  495. this._vertexBuffers[VertexBuffer.ColorKind] = colors;
  496. dataOffset += 4;
  497. var options = this._vertexBuffer.createVertexBuffer("angle", dataOffset, 1, this._vertexBufferSize, this._useInstancing);
  498. this._vertexBuffers["angle"] = options;
  499. dataOffset += 1;
  500. var size = this._vertexBuffer.createVertexBuffer("size", dataOffset, 2, this._vertexBufferSize, this._useInstancing);
  501. this._vertexBuffers["size"] = size;
  502. dataOffset += 2;
  503. if (this._isAnimationSheetEnabled) {
  504. var cellIndexBuffer = this._vertexBuffer.createVertexBuffer("cellIndex", dataOffset, 1, this._vertexBufferSize, this._useInstancing);
  505. this._vertexBuffers["cellIndex"] = cellIndexBuffer;
  506. dataOffset += 1;
  507. }
  508. if (!this._isBillboardBased) {
  509. var directionBuffer = this._vertexBuffer.createVertexBuffer("direction", dataOffset, 3, this._vertexBufferSize, this._useInstancing);
  510. this._vertexBuffers["direction"] = directionBuffer;
  511. dataOffset += 3;
  512. }
  513. var offsets: VertexBuffer;
  514. if (this._useInstancing) {
  515. var spriteData = new Float32Array([0, 0, 1, 0, 1, 1, 0, 1]);
  516. this._spriteBuffer = new Buffer(engine, spriteData, false, 2);
  517. offsets = this._spriteBuffer.createVertexBuffer("offset", 0, 2);
  518. } else {
  519. offsets = this._vertexBuffer.createVertexBuffer("offset", dataOffset, 2, this._vertexBufferSize, this._useInstancing);
  520. dataOffset += 2;
  521. }
  522. this._vertexBuffers["offset"] = offsets;
  523. }
  524. private _createIndexBuffer() {
  525. if (this._useInstancing) {
  526. return;
  527. }
  528. var indices = [];
  529. var index = 0;
  530. for (var count = 0; count < this._capacity; count++) {
  531. indices.push(index);
  532. indices.push(index + 1);
  533. indices.push(index + 2);
  534. indices.push(index);
  535. indices.push(index + 2);
  536. indices.push(index + 3);
  537. index += 4;
  538. }
  539. this._indexBuffer = this._scene.getEngine().createIndexBuffer(indices);
  540. }
  541. /**
  542. * Gets the maximum number of particles active at the same time.
  543. * @returns The max number of active particles.
  544. */
  545. public getCapacity(): number {
  546. return this._capacity;
  547. }
  548. /**
  549. * Gets whether there are still active particles in the system.
  550. * @returns True if it is alive, otherwise false.
  551. */
  552. public isAlive(): boolean {
  553. return this._alive;
  554. }
  555. /**
  556. * Gets if the system has been started. (Note: this will still be true after stop is called)
  557. * @returns True if it has been started, otherwise false.
  558. */
  559. public isStarted(): boolean {
  560. return this._started;
  561. }
  562. /**
  563. * Starts the particle system and begins to emit
  564. * @param delay defines the delay in milliseconds before starting the system (0 by default)
  565. */
  566. public start(delay = 0): void {
  567. if (delay) {
  568. setTimeout(()=> {
  569. this.start(0);
  570. }, delay);
  571. return;
  572. }
  573. this._started = true;
  574. this._stopped = false;
  575. this._actualFrame = 0;
  576. if (this.subEmitters && this.subEmitters.length != 0) {
  577. this.activeSubSystems = new Array<ParticleSystem>();
  578. }
  579. if (this.preWarmCycles) {
  580. for (var index = 0; index < this.preWarmCycles; index++) {
  581. this.animate(true);
  582. }
  583. }
  584. }
  585. /**
  586. * Stops the particle system.
  587. * @param stopSubEmitters if true it will stop the current system and all created sub-Systems if false it will stop the current root system only, this param is used by the root particle system only. the default value is true.
  588. */
  589. public stop(stopSubEmitters = true): void {
  590. this._stopped = true;
  591. if (stopSubEmitters) {
  592. this._stopSubEmitters();
  593. }
  594. }
  595. // animation sheet
  596. /**
  597. * Remove all active particles
  598. */
  599. public reset(): void {
  600. this._stockParticles = [];
  601. this._particles = [];
  602. }
  603. /**
  604. * @hidden (for internal use only)
  605. */
  606. public _appendParticleVertex(index: number, particle: Particle, offsetX: number, offsetY: number): void {
  607. var offset = index * this._vertexBufferSize;
  608. this._vertexData[offset++] = particle.position.x;
  609. this._vertexData[offset++] = particle.position.y;
  610. this._vertexData[offset++] = particle.position.z;
  611. this._vertexData[offset++] = particle.color.r;
  612. this._vertexData[offset++] = particle.color.g;
  613. this._vertexData[offset++] = particle.color.b;
  614. this._vertexData[offset++] = particle.color.a;
  615. this._vertexData[offset++] = particle.angle;
  616. this._vertexData[offset++] = particle.scale.x * particle.size;
  617. this._vertexData[offset++] = particle.scale.y * particle.size;
  618. if (this._isAnimationSheetEnabled) {
  619. this._vertexData[offset++] = particle.cellIndex;
  620. }
  621. if (!this._isBillboardBased) {
  622. if (particle._initialDirection) {
  623. this._vertexData[offset++] = particle._initialDirection.x;
  624. this._vertexData[offset++] = particle._initialDirection.y;
  625. this._vertexData[offset++] = particle._initialDirection.z;
  626. } else {
  627. this._vertexData[offset++] = particle.direction.x;
  628. this._vertexData[offset++] = particle.direction.y;
  629. this._vertexData[offset++] = particle.direction.z;
  630. }
  631. }
  632. if (!this._useInstancing) {
  633. if (this._isAnimationSheetEnabled) {
  634. if (offsetX === 0)
  635. offsetX = this._epsilon;
  636. else if (offsetX === 1)
  637. offsetX = 1 - this._epsilon;
  638. if (offsetY === 0)
  639. offsetY = this._epsilon;
  640. else if (offsetY === 1)
  641. offsetY = 1 - this._epsilon;
  642. }
  643. this._vertexData[offset++] = offsetX;
  644. this._vertexData[offset++] = offsetY;
  645. }
  646. }
  647. // start of sub system methods
  648. /**
  649. * "Recycles" one of the particle by copying it back to the "stock" of particles and removing it from the active list.
  650. * Its lifetime will start back at 0.
  651. */
  652. public recycleParticle: (particle: Particle) => void = (particle) => {
  653. var lastParticle = <Particle>this._particles.pop();
  654. if (lastParticle !== particle) {
  655. lastParticle.copyTo(particle);
  656. }
  657. this._stockParticles.push(lastParticle);
  658. };
  659. private _stopSubEmitters(): void {
  660. if (!this.activeSubSystems) {
  661. return;
  662. }
  663. this.activeSubSystems.forEach(subSystem => {
  664. subSystem.stop(true);
  665. });
  666. this.activeSubSystems = new Array<ParticleSystem>();
  667. }
  668. private _createParticle: () => Particle = () => {
  669. var particle: Particle;
  670. if (this._stockParticles.length !== 0) {
  671. particle = <Particle>this._stockParticles.pop();
  672. particle.age = 0;
  673. particle._currentColorGradient = null;
  674. particle.cellIndex = this.startSpriteCellID;
  675. } else {
  676. particle = new Particle(this);
  677. }
  678. return particle;
  679. }
  680. private _removeFromRoot(): void {
  681. if (!this._rootParticleSystem){
  682. return;
  683. }
  684. let index = this._rootParticleSystem.activeSubSystems.indexOf(this);
  685. if (index !== -1) {
  686. this._rootParticleSystem.activeSubSystems.splice(index, 1);
  687. }
  688. }
  689. private _emitFromParticle: (particle: Particle) => void = (particle) => {
  690. if (!this.subEmitters || this.subEmitters.length === 0) {
  691. return;
  692. }
  693. var templateIndex = Math.floor(Math.random() * this.subEmitters.length);
  694. var subSystem = this.subEmitters[templateIndex].clone(this.name + "_sub", particle.position.clone());
  695. subSystem._rootParticleSystem = this;
  696. this.activeSubSystems.push(subSystem);
  697. subSystem.start();
  698. }
  699. // End of sub system methods
  700. private _update(newParticles: number): void {
  701. // Update current
  702. this._alive = this._particles.length > 0;
  703. if ((<AbstractMesh>this.emitter).position) {
  704. var emitterMesh = (<AbstractMesh>this.emitter);
  705. this._emitterWorldMatrix = emitterMesh.getWorldMatrix();
  706. } else {
  707. var emitterPosition = (<Vector3>this.emitter);
  708. this._emitterWorldMatrix = Matrix.Translation(emitterPosition.x, emitterPosition.y, emitterPosition.z);
  709. }
  710. this.updateFunction(this._particles);
  711. // Add new ones
  712. var particle: Particle;
  713. for (var index = 0; index < newParticles; index++) {
  714. if (this._particles.length === this._capacity) {
  715. break;
  716. }
  717. particle = this._createParticle();
  718. this._particles.push(particle);
  719. // Emitter
  720. let emitPower = Scalar.RandomRange(this.minEmitPower, this.maxEmitPower);
  721. if (this.startPositionFunction) {
  722. this.startPositionFunction(this._emitterWorldMatrix, particle.position, particle);
  723. }
  724. else {
  725. this.particleEmitterType.startPositionFunction(this._emitterWorldMatrix, particle.position, particle);
  726. }
  727. if (this.startDirectionFunction) {
  728. this.startDirectionFunction(this._emitterWorldMatrix, particle.direction, particle);
  729. }
  730. else {
  731. this.particleEmitterType.startDirectionFunction(this._emitterWorldMatrix, particle.direction, particle);
  732. }
  733. if (emitPower === 0) {
  734. if (!particle._initialDirection) {
  735. particle._initialDirection = particle.direction.clone();
  736. } else {
  737. particle._initialDirection.copyFrom(particle.direction);
  738. }
  739. } else {
  740. particle._initialDirection = null;
  741. }
  742. particle.direction.scaleInPlace(emitPower);
  743. // Life time
  744. if (this.targetStopDuration && this._lifeTimeGradients && this._lifeTimeGradients.length > 0) {
  745. let ratio = Scalar.Clamp(this._actualFrame / this.targetStopDuration);
  746. Tools.GetCurrentGradient(ratio, this._lifeTimeGradients, (currentGradient, nextGradient, scale) => {
  747. let factorGradient1 = (<FactorGradient>currentGradient);
  748. let factorGradient2 = (<FactorGradient>nextGradient);
  749. let lifeTime1 = factorGradient1.getFactor();
  750. let lifeTime2 = factorGradient2.getFactor();
  751. let gradient = (ratio - factorGradient1.gradient) / (factorGradient2.gradient - factorGradient1.gradient);
  752. particle.lifeTime = Scalar.Lerp(lifeTime1, lifeTime2, gradient);
  753. });
  754. } else {
  755. particle.lifeTime = Scalar.RandomRange(this.minLifeTime, this.maxLifeTime);
  756. }
  757. // Size
  758. if (!this._sizeGradients || this._sizeGradients.length === 0) {
  759. particle.size = Scalar.RandomRange(this.minSize, this.maxSize);
  760. } else {
  761. particle._currentSizeGradient = this._sizeGradients[0];
  762. particle._currentSize1 = particle._currentSizeGradient.getFactor();
  763. particle.size = particle._currentSize1;
  764. if (this._sizeGradients.length > 1) {
  765. particle._currentSize2 = this._sizeGradients[1].getFactor();
  766. } else {
  767. particle._currentSize2 = particle._currentSize1;
  768. }
  769. }
  770. // Size and scale
  771. particle.scale.copyFromFloats(Scalar.RandomRange(this.minScaleX, this.maxScaleX), Scalar.RandomRange(this.minScaleY, this.maxScaleY));
  772. // Angle
  773. if (!this._angularSpeedGradients || this._angularSpeedGradients.length === 0) {
  774. particle.angularSpeed = Scalar.RandomRange(this.minAngularSpeed, this.maxAngularSpeed);
  775. } else {
  776. particle._currentAngularSpeedGradient = this._angularSpeedGradients[0];
  777. particle.angularSpeed = particle._currentAngularSpeedGradient.getFactor();
  778. particle._currentAngularSpeed1 = particle.angularSpeed;
  779. if (this._angularSpeedGradients.length > 1) {
  780. particle._currentAngularSpeed2 = this._angularSpeedGradients[1].getFactor();
  781. } else {
  782. particle._currentAngularSpeed2 = particle._currentAngularSpeed1;
  783. }
  784. }
  785. particle.angle = Scalar.RandomRange(this.minInitialRotation, this.maxInitialRotation);
  786. // Velocity
  787. if (this._velocityGradients && this._velocityGradients.length > 0) {
  788. particle._currentVelocityGradient = this._velocityGradients[0];
  789. particle._currentVelocity1 = particle._currentVelocityGradient.getFactor();
  790. if (this._velocityGradients.length > 1) {
  791. particle._currentVelocity2 = this._velocityGradients[1].getFactor();
  792. } else {
  793. particle._currentVelocity2 = particle._currentVelocity1;
  794. }
  795. }
  796. // Limit velocity
  797. if (this._limitVelocityGradients && this._limitVelocityGradients.length > 0) {
  798. particle._currentLimitVelocityGradient = this._limitVelocityGradients[0];
  799. particle._currentLimitVelocity1 = particle._currentLimitVelocityGradient.getFactor();
  800. if (this._limitVelocityGradients.length > 1) {
  801. particle._currentLimitVelocity2 = this._limitVelocityGradients[1].getFactor();
  802. } else {
  803. particle._currentLimitVelocity2 = particle._currentLimitVelocity1;
  804. }
  805. }
  806. // Drag
  807. if (this._dragGradients && this._dragGradients.length > 0) {
  808. particle._currentDragGradient = this._dragGradients[0];
  809. particle._currentDrag1 = particle._currentDragGradient.getFactor();
  810. if (this._dragGradients.length > 1) {
  811. particle._currentDrag2 = this._dragGradients[1].getFactor();
  812. } else {
  813. particle._currentDrag2 = particle._currentDrag1;
  814. }
  815. }
  816. // Color
  817. if (!this._colorGradients || this._colorGradients.length === 0) {
  818. var step = Scalar.RandomRange(0, 1.0);
  819. Color4.LerpToRef(this.color1, this.color2, step, particle.color);
  820. this.colorDead.subtractToRef(particle.color, this._colorDiff);
  821. this._colorDiff.scaleToRef(1.0 / particle.lifeTime, particle.colorStep);
  822. } else {
  823. particle._currentColorGradient = this._colorGradients[0];
  824. particle._currentColorGradient.getColorToRef(particle.color);
  825. particle._currentColor1.copyFrom(particle.color);
  826. if (this._colorGradients.length > 1) {
  827. this._colorGradients[1].getColorToRef(particle._currentColor2);
  828. } else {
  829. particle._currentColor2.copyFrom(particle.color);
  830. }
  831. }
  832. // Sheet
  833. if (this._isAnimationSheetEnabled) {
  834. particle._initialStartSpriteCellID = this.startSpriteCellID;
  835. particle._initialEndSpriteCellID = this.endSpriteCellID;
  836. }
  837. }
  838. }
  839. /** @hidden */
  840. public static _GetAttributeNamesOrOptions(isAnimationSheetEnabled = false, isBillboardBased = false): string[] {
  841. var attributeNamesOrOptions = [VertexBuffer.PositionKind, VertexBuffer.ColorKind, "angle", "offset", "size"];
  842. if (isAnimationSheetEnabled) {
  843. attributeNamesOrOptions.push("cellIndex");
  844. }
  845. if (!isBillboardBased) {
  846. attributeNamesOrOptions.push("direction");
  847. }
  848. return attributeNamesOrOptions;
  849. }
  850. public static _GetEffectCreationOptions(isAnimationSheetEnabled = false): string[] {
  851. var effectCreationOption = ["invView", "view", "projection", "vClipPlane", "vClipPlane2", "vClipPlane3", "vClipPlane4", "textureMask", "translationPivot", "eyePosition"];
  852. if (isAnimationSheetEnabled) {
  853. effectCreationOption.push("particlesInfos")
  854. }
  855. return effectCreationOption;
  856. }
  857. private _getEffect(): Effect {
  858. if (this._customEffect) {
  859. return this._customEffect;
  860. };
  861. var defines = [];
  862. if (this._scene.clipPlane) {
  863. defines.push("#define CLIPPLANE");
  864. }
  865. if (this._scene.clipPlane2) {
  866. defines.push("#define CLIPPLANE2");
  867. }
  868. if (this._scene.clipPlane3) {
  869. defines.push("#define CLIPPLANE3");
  870. }
  871. if (this._scene.clipPlane4) {
  872. defines.push("#define CLIPPLANE4");
  873. }
  874. if (this._isAnimationSheetEnabled) {
  875. defines.push("#define ANIMATESHEET");
  876. }
  877. if (this._isBillboardBased) {
  878. defines.push("#define BILLBOARD");
  879. switch (this.billboardMode) {
  880. case AbstractMesh.BILLBOARDMODE_Y:
  881. defines.push("#define BILLBOARDY");
  882. break;
  883. case AbstractMesh.BILLBOARDMODE_ALL:
  884. default:
  885. break;
  886. }
  887. }
  888. if (this._imageProcessingConfiguration) {
  889. this._imageProcessingConfiguration.prepareDefines(this._imageProcessingConfigurationDefines);
  890. defines.push(this._imageProcessingConfigurationDefines.toString());
  891. }
  892. // Effect
  893. var join = defines.join("\n");
  894. if (this._cachedDefines !== join) {
  895. this._cachedDefines = join;
  896. var attributesNamesOrOptions = ParticleSystem._GetAttributeNamesOrOptions(this._isAnimationSheetEnabled, this._isBillboardBased);
  897. var effectCreationOption = ParticleSystem._GetEffectCreationOptions(this._isAnimationSheetEnabled);
  898. var samplers = ["diffuseSampler"];
  899. if (ImageProcessingConfiguration) {
  900. ImageProcessingConfiguration.PrepareUniforms(effectCreationOption, this._imageProcessingConfigurationDefines);
  901. ImageProcessingConfiguration.PrepareSamplers(samplers, this._imageProcessingConfigurationDefines);
  902. }
  903. this._effect = this._scene.getEngine().createEffect(
  904. "particles",
  905. attributesNamesOrOptions,
  906. effectCreationOption,
  907. ["diffuseSampler"], join);
  908. }
  909. return this._effect;
  910. }
  911. /**
  912. * Animates the particle system for the current frame by emitting new particles and or animating the living ones.
  913. * @param preWarmOnly will prevent the system from updating the vertex buffer (default is false)
  914. */
  915. public animate(preWarmOnly = false): void {
  916. if (!this._started)
  917. return;
  918. if (!preWarmOnly) {
  919. var effect = this._getEffect();
  920. // Check
  921. if (!this.emitter || !this._imageProcessingConfiguration.isReady() || !effect.isReady() || !this.particleTexture || !this.particleTexture.isReady())
  922. return;
  923. if (this._currentRenderId === this._scene.getRenderId()) {
  924. return;
  925. }
  926. this._currentRenderId = this._scene.getRenderId();
  927. }
  928. this._scaledUpdateSpeed = this.updateSpeed * (preWarmOnly ? this.preWarmStepOffset : this._scene.getAnimationRatio());
  929. // determine the number of particles we need to create
  930. var newParticles;
  931. if (this.manualEmitCount > -1) {
  932. newParticles = this.manualEmitCount;
  933. this._newPartsExcess = 0;
  934. this.manualEmitCount = 0;
  935. } else {
  936. newParticles = ((this.emitRate * this._scaledUpdateSpeed) >> 0);
  937. this._newPartsExcess += this.emitRate * this._scaledUpdateSpeed - newParticles;
  938. }
  939. if (this._newPartsExcess > 1.0) {
  940. newParticles += this._newPartsExcess >> 0;
  941. this._newPartsExcess -= this._newPartsExcess >> 0;
  942. }
  943. this._alive = false;
  944. if (!this._stopped) {
  945. this._actualFrame += this._scaledUpdateSpeed;
  946. if (this.targetStopDuration && this._actualFrame >= this.targetStopDuration)
  947. this.stop();
  948. } else {
  949. newParticles = 0;
  950. }
  951. this._update(newParticles);
  952. // Stopped?
  953. if (this._stopped) {
  954. if (!this._alive) {
  955. this._started = false;
  956. if (this.onAnimationEnd) {
  957. this.onAnimationEnd();
  958. }
  959. if (this.disposeOnStop) {
  960. this._scene._toBeDisposed.push(this);
  961. }
  962. }
  963. }
  964. if (!preWarmOnly) {
  965. // Update VBO
  966. var offset = 0;
  967. for (var index = 0; index < this._particles.length; index++) {
  968. var particle = this._particles[index];
  969. this._appendParticleVertices(offset, particle);
  970. offset += this._useInstancing ? 1 : 4;
  971. }
  972. if (this._vertexBuffer) {
  973. this._vertexBuffer.update(this._vertexData);
  974. }
  975. }
  976. if (this.manualEmitCount === 0 && this.disposeOnStop) {
  977. this.stop();
  978. }
  979. }
  980. private _appendParticleVertices(offset: number, particle: Particle) {
  981. this._appendParticleVertex(offset++, particle, 0, 0);
  982. if (!this._useInstancing) {
  983. this._appendParticleVertex(offset++, particle, 1, 0);
  984. this._appendParticleVertex(offset++, particle, 1, 1);
  985. this._appendParticleVertex(offset++, particle, 0, 1);
  986. }
  987. }
  988. /**
  989. * Rebuilds the particle system.
  990. */
  991. public rebuild(): void {
  992. this._createIndexBuffer();
  993. if (this._vertexBuffer) {
  994. this._vertexBuffer._rebuild();
  995. }
  996. }
  997. /**
  998. * Is this system ready to be used/rendered
  999. * @return true if the system is ready
  1000. */
  1001. public isReady(): boolean {
  1002. var effect = this._getEffect();
  1003. if (!this.emitter || !this._imageProcessingConfiguration.isReady() || !effect.isReady() || !this.particleTexture || !this.particleTexture.isReady()) {
  1004. return false;
  1005. }
  1006. return true;
  1007. }
  1008. /**
  1009. * Renders the particle system in its current state.
  1010. * @returns the current number of particles
  1011. */
  1012. public render(): number {
  1013. var effect = this._getEffect();
  1014. // Check
  1015. if (!this.isReady() || !this._particles.length) {
  1016. return 0;
  1017. }
  1018. var engine = this._scene.getEngine();
  1019. // Render
  1020. engine.enableEffect(effect);
  1021. engine.setState(false);
  1022. var viewMatrix = this._scene.getViewMatrix();
  1023. effect.setTexture("diffuseSampler", this.particleTexture);
  1024. effect.setMatrix("view", viewMatrix);
  1025. effect.setMatrix("projection", this._scene.getProjectionMatrix());
  1026. if (this._isAnimationSheetEnabled && this.particleTexture) {
  1027. var baseSize = this.particleTexture.getBaseSize();
  1028. effect.setFloat3("particlesInfos", this.spriteCellWidth / baseSize.width, this.spriteCellHeight / baseSize.height, baseSize.width / this.spriteCellWidth);
  1029. }
  1030. effect.setVector2("translationPivot", this.translationPivot);
  1031. effect.setFloat4("textureMask", this.textureMask.r, this.textureMask.g, this.textureMask.b, this.textureMask.a);
  1032. if (this._isBillboardBased) {
  1033. var camera = this._scene.activeCamera!;
  1034. effect.setVector3("eyePosition", camera.globalPosition);
  1035. }
  1036. if (this._scene.clipPlane || this._scene.clipPlane2 || this._scene.clipPlane3 || this._scene.clipPlane4) {
  1037. var invView = viewMatrix.clone();
  1038. invView.invert();
  1039. effect.setMatrix("invView", invView);
  1040. MaterialHelper.BindClipPlane(effect, this._scene);
  1041. }
  1042. engine.bindBuffers(this._vertexBuffers, this._indexBuffer, effect);
  1043. // image processing
  1044. if (this._imageProcessingConfiguration && !this._imageProcessingConfiguration.applyByPostProcess) {
  1045. this._imageProcessingConfiguration.bind(effect);
  1046. }
  1047. // Draw order
  1048. switch(this.blendMode)
  1049. {
  1050. case ParticleSystem.BLENDMODE_ADD:
  1051. engine.setAlphaMode(Engine.ALPHA_ADD);
  1052. break;
  1053. case ParticleSystem.BLENDMODE_ONEONE:
  1054. engine.setAlphaMode(Engine.ALPHA_ONEONE);
  1055. break;
  1056. case ParticleSystem.BLENDMODE_STANDARD:
  1057. engine.setAlphaMode(Engine.ALPHA_COMBINE);
  1058. break;
  1059. }
  1060. if (this.forceDepthWrite) {
  1061. engine.setDepthWrite(true);
  1062. }
  1063. if (this._useInstancing) {
  1064. engine.drawArraysType(Material.TriangleFanDrawMode, 0, 4, this._particles.length);
  1065. engine.unbindInstanceAttributes();
  1066. } else {
  1067. engine.drawElementsType(Material.TriangleFillMode, 0, this._particles.length * 6);
  1068. }
  1069. engine.setAlphaMode(Engine.ALPHA_DISABLE);
  1070. return this._particles.length;
  1071. }
  1072. /**
  1073. * Disposes the particle system and free the associated resources
  1074. * @param disposeTexture defines if the particule texture must be disposed as well (true by default)
  1075. */
  1076. public dispose(disposeTexture = true): void {
  1077. if (this._vertexBuffer) {
  1078. this._vertexBuffer.dispose();
  1079. this._vertexBuffer = null;
  1080. }
  1081. if (this._spriteBuffer) {
  1082. this._spriteBuffer.dispose();
  1083. this._spriteBuffer = null;
  1084. }
  1085. if (this._indexBuffer) {
  1086. this._scene.getEngine()._releaseBuffer(this._indexBuffer);
  1087. this._indexBuffer = null;
  1088. }
  1089. if (disposeTexture && this.particleTexture) {
  1090. this.particleTexture.dispose();
  1091. this.particleTexture = null;
  1092. }
  1093. if (disposeTexture && this.noiseTexture) {
  1094. this.noiseTexture.dispose();
  1095. this.noiseTexture = null;
  1096. }
  1097. this._removeFromRoot();
  1098. // Remove from scene
  1099. var index = this._scene.particleSystems.indexOf(this);
  1100. if (index > -1) {
  1101. this._scene.particleSystems.splice(index, 1);
  1102. }
  1103. // Callback
  1104. this.onDisposeObservable.notifyObservers(this);
  1105. this.onDisposeObservable.clear();
  1106. }
  1107. // Clone
  1108. /**
  1109. * Clones the particle system.
  1110. * @param name The name of the cloned object
  1111. * @param newEmitter The new emitter to use
  1112. * @returns the cloned particle system
  1113. */
  1114. public clone(name: string, newEmitter: any): ParticleSystem {
  1115. var custom: Nullable<Effect> = null;
  1116. var program: any = null;
  1117. if (this.customShader != null) {
  1118. program = this.customShader;
  1119. var defines: string = (program.shaderOptions.defines.length > 0) ? program.shaderOptions.defines.join("\n") : "";
  1120. custom = this._scene.getEngine().createEffectForParticles(program.shaderPath.fragmentElement, program.shaderOptions.uniforms, program.shaderOptions.samplers, defines);
  1121. } else if (this._customEffect) {
  1122. custom = this._customEffect;
  1123. }
  1124. var result = new ParticleSystem(name, this._capacity, this._scene, custom);
  1125. result.customShader = program;
  1126. Tools.DeepCopy(this, result, ["particles", "customShader"]);
  1127. if (newEmitter === undefined) {
  1128. newEmitter = this.emitter;
  1129. }
  1130. result.emitter = newEmitter;
  1131. if (this.particleTexture) {
  1132. result.particleTexture = new Texture(this.particleTexture.url, this._scene);
  1133. }
  1134. if (!this.preventAutoStart) {
  1135. result.start();
  1136. }
  1137. return result;
  1138. }
  1139. /**
  1140. * Serializes the particle system to a JSON object.
  1141. * @returns the JSON object
  1142. */
  1143. public serialize(): any {
  1144. var serializationObject: any = {};
  1145. ParticleSystem._Serialize(serializationObject, this);
  1146. serializationObject.textureMask = this.textureMask.asArray();
  1147. serializationObject.customShader = this.customShader;
  1148. serializationObject.preventAutoStart = this.preventAutoStart;
  1149. serializationObject.isAnimationSheetEnabled = this._isAnimationSheetEnabled;
  1150. return serializationObject;
  1151. }
  1152. /** @hidden */
  1153. public static _Serialize(serializationObject: any, particleSystem: IParticleSystem) {
  1154. serializationObject.name = particleSystem.name;
  1155. serializationObject.id = particleSystem.id;
  1156. serializationObject.capacity = particleSystem.getCapacity();
  1157. // Emitter
  1158. if ((<AbstractMesh>particleSystem.emitter).position) {
  1159. var emitterMesh = (<AbstractMesh>particleSystem.emitter);
  1160. serializationObject.emitterId = emitterMesh.id;
  1161. } else {
  1162. var emitterPosition = (<Vector3>particleSystem.emitter);
  1163. serializationObject.emitter = emitterPosition.asArray();
  1164. }
  1165. // Emitter
  1166. if (particleSystem.particleEmitterType) {
  1167. serializationObject.particleEmitterType = particleSystem.particleEmitterType.serialize();
  1168. }
  1169. if (particleSystem.particleTexture) {
  1170. serializationObject.textureName = particleSystem.particleTexture.name;
  1171. }
  1172. // Animations
  1173. Animation.AppendSerializedAnimations(particleSystem, serializationObject);
  1174. // Particle system
  1175. serializationObject.renderingGroupId = particleSystem.renderingGroupId;
  1176. serializationObject.isBillboardBased = particleSystem.isBillboardBased;
  1177. serializationObject.minAngularSpeed = particleSystem.minAngularSpeed;
  1178. serializationObject.maxAngularSpeed = particleSystem.maxAngularSpeed;
  1179. serializationObject.minSize = particleSystem.minSize;
  1180. serializationObject.maxSize = particleSystem.maxSize;
  1181. serializationObject.minScaleX = particleSystem.minScaleX;
  1182. serializationObject.maxScaleX = particleSystem.maxScaleX;
  1183. serializationObject.minScaleY = particleSystem.minScaleY;
  1184. serializationObject.maxScaleY = particleSystem.maxScaleY;
  1185. serializationObject.minEmitPower = particleSystem.minEmitPower;
  1186. serializationObject.maxEmitPower = particleSystem.maxEmitPower;
  1187. serializationObject.minLifeTime = particleSystem.minLifeTime;
  1188. serializationObject.maxLifeTime = particleSystem.maxLifeTime;
  1189. serializationObject.emitRate = particleSystem.emitRate;
  1190. serializationObject.gravity = particleSystem.gravity.asArray();
  1191. serializationObject.noiseStrength = particleSystem.noiseStrength.asArray();
  1192. serializationObject.color1 = particleSystem.color1.asArray();
  1193. serializationObject.color2 = particleSystem.color2.asArray();
  1194. serializationObject.colorDead = particleSystem.colorDead.asArray();
  1195. serializationObject.updateSpeed = particleSystem.updateSpeed;
  1196. serializationObject.targetStopDuration = particleSystem.targetStopDuration;
  1197. serializationObject.blendMode = particleSystem.blendMode;
  1198. serializationObject.preWarmCycles = particleSystem.preWarmCycles;
  1199. serializationObject.preWarmStepOffset = particleSystem.preWarmStepOffset;
  1200. serializationObject.minInitialRotation = particleSystem.minInitialRotation;
  1201. serializationObject.maxInitialRotation = particleSystem.maxInitialRotation;
  1202. serializationObject.startSpriteCellID = particleSystem.startSpriteCellID;
  1203. serializationObject.endSpriteCellID = particleSystem.endSpriteCellID;
  1204. serializationObject.spriteCellChangeSpeed = particleSystem.spriteCellChangeSpeed;
  1205. serializationObject.spriteCellWidth = particleSystem.spriteCellWidth;
  1206. serializationObject.spriteCellHeight = particleSystem.spriteCellHeight;
  1207. let colorGradients = particleSystem.getColorGradients();
  1208. if (colorGradients) {
  1209. serializationObject.colorGradients = [];
  1210. for (var colorGradient of colorGradients) {
  1211. var serializedGradient: any = {
  1212. gradient: colorGradient.gradient,
  1213. color1: colorGradient.color1.asArray()
  1214. };
  1215. if (colorGradient.color2) {
  1216. serializedGradient.color2 = colorGradient.color2.asArray();
  1217. }
  1218. serializationObject.colorGradients.push(serializedGradient);
  1219. }
  1220. }
  1221. let sizeGradients = particleSystem.getSizeGradients();
  1222. if (sizeGradients) {
  1223. serializationObject.sizeGradients = [];
  1224. for (var sizeGradient of sizeGradients) {
  1225. var serializedGradient: any = {
  1226. gradient: sizeGradient.gradient,
  1227. factor1: sizeGradient.factor1
  1228. };
  1229. if (sizeGradient.factor2 !== undefined) {
  1230. serializedGradient.factor2 = sizeGradient.factor2;
  1231. }
  1232. serializationObject.sizeGradients.push(serializedGradient);
  1233. }
  1234. }
  1235. let angularSpeedGradients = particleSystem.getAngularSpeedGradients();
  1236. if (angularSpeedGradients) {
  1237. serializationObject.angularSpeedGradients = [];
  1238. for (var angularSpeedGradient of angularSpeedGradients) {
  1239. var serializedGradient: any = {
  1240. gradient: angularSpeedGradient.gradient,
  1241. factor1: angularSpeedGradient.factor1
  1242. };
  1243. if (angularSpeedGradient.factor2 !== undefined) {
  1244. serializedGradient.factor2 = angularSpeedGradient.factor2;
  1245. }
  1246. serializationObject.angularSpeedGradients.push(serializedGradient);
  1247. }
  1248. }
  1249. let velocityGradients = particleSystem.getVelocityGradients();
  1250. if (velocityGradients) {
  1251. serializationObject.velocityGradients = [];
  1252. for (var velocityGradient of velocityGradients) {
  1253. var serializedGradient: any = {
  1254. gradient: velocityGradient.gradient,
  1255. factor1: velocityGradient.factor1
  1256. };
  1257. if (velocityGradient.factor2 !== undefined) {
  1258. serializedGradient.factor2 = velocityGradient.factor2;
  1259. }
  1260. serializationObject.velocityGradients.push(serializedGradient);
  1261. }
  1262. }
  1263. let limitVelocityGradients = particleSystem.getLimitVelocityGradients();
  1264. if (limitVelocityGradients) {
  1265. serializationObject.limitVelocityGradients = [];
  1266. for (var limitVelocityGradient of limitVelocityGradients) {
  1267. var serializedGradient: any = {
  1268. gradient: limitVelocityGradient.gradient,
  1269. factor1: limitVelocityGradient.factor1
  1270. };
  1271. if (limitVelocityGradient.factor2 !== undefined) {
  1272. serializedGradient.factor2 = limitVelocityGradient.factor2;
  1273. }
  1274. serializationObject.limitVelocityGradients.push(serializedGradient);
  1275. }
  1276. serializationObject.limitVelocityDamping = particleSystem.limitVelocityDamping;
  1277. }
  1278. if (particleSystem.noiseTexture && particleSystem.noiseTexture instanceof ProceduralTexture) {
  1279. const noiseTexture = particleSystem.noiseTexture as ProceduralTexture;
  1280. serializationObject.noiseTexture = noiseTexture.serialize();
  1281. }
  1282. }
  1283. /** @hidden */
  1284. public static _Parse(parsedParticleSystem: any, particleSystem: IParticleSystem, scene: Scene, rootUrl: string) {
  1285. // Texture
  1286. if (parsedParticleSystem.textureName) {
  1287. particleSystem.particleTexture = new Texture(rootUrl + parsedParticleSystem.textureName, scene);
  1288. particleSystem.particleTexture.name = parsedParticleSystem.textureName;
  1289. }
  1290. // Emitter
  1291. if (parsedParticleSystem.emitterId === undefined) {
  1292. particleSystem.emitter = Vector3.Zero();
  1293. }
  1294. else if (parsedParticleSystem.emitterId) {
  1295. particleSystem.emitter = scene.getLastMeshByID(parsedParticleSystem.emitterId);
  1296. } else {
  1297. particleSystem.emitter = Vector3.FromArray(parsedParticleSystem.emitter);
  1298. }
  1299. // Misc.
  1300. if (parsedParticleSystem.renderingGroupId !== undefined) {
  1301. particleSystem.renderingGroupId = parsedParticleSystem.renderingGroupId;
  1302. }
  1303. if (parsedParticleSystem.isBillboardBased !== undefined) {
  1304. particleSystem.isBillboardBased = parsedParticleSystem.isBillboardBased;
  1305. }
  1306. // Animations
  1307. if (parsedParticleSystem.animations) {
  1308. for (var animationIndex = 0; animationIndex < parsedParticleSystem.animations.length; animationIndex++) {
  1309. var parsedAnimation = parsedParticleSystem.animations[animationIndex];
  1310. particleSystem.animations.push(Animation.Parse(parsedAnimation));
  1311. }
  1312. }
  1313. if (parsedParticleSystem.autoAnimate) {
  1314. scene.beginAnimation(particleSystem, parsedParticleSystem.autoAnimateFrom, parsedParticleSystem.autoAnimateTo, parsedParticleSystem.autoAnimateLoop, parsedParticleSystem.autoAnimateSpeed || 1.0);
  1315. }
  1316. // Particle system
  1317. particleSystem.minAngularSpeed = parsedParticleSystem.minAngularSpeed;
  1318. particleSystem.maxAngularSpeed = parsedParticleSystem.maxAngularSpeed;
  1319. particleSystem.minSize = parsedParticleSystem.minSize;
  1320. particleSystem.maxSize = parsedParticleSystem.maxSize;
  1321. if (parsedParticleSystem.minScaleX) {
  1322. particleSystem.minScaleX = parsedParticleSystem.minScaleX;
  1323. particleSystem.maxScaleX = parsedParticleSystem.maxScaleX;
  1324. particleSystem.minScaleY = parsedParticleSystem.minScaleY;
  1325. particleSystem.maxScaleY = parsedParticleSystem.maxScaleY;
  1326. }
  1327. if (parsedParticleSystem.preWarmCycles !== undefined) {
  1328. particleSystem.preWarmCycles = parsedParticleSystem.preWarmCycles;
  1329. particleSystem.preWarmStepOffset = parsedParticleSystem.preWarmStepOffset;
  1330. }
  1331. if (parsedParticleSystem.minInitialRotation !== undefined) {
  1332. particleSystem.minInitialRotation = parsedParticleSystem.minInitialRotation;
  1333. particleSystem.maxInitialRotation = parsedParticleSystem.maxInitialRotation;
  1334. }
  1335. particleSystem.minLifeTime = parsedParticleSystem.minLifeTime;
  1336. particleSystem.maxLifeTime = parsedParticleSystem.maxLifeTime;
  1337. particleSystem.minEmitPower = parsedParticleSystem.minEmitPower;
  1338. particleSystem.maxEmitPower = parsedParticleSystem.maxEmitPower;
  1339. particleSystem.emitRate = parsedParticleSystem.emitRate;
  1340. particleSystem.gravity = Vector3.FromArray(parsedParticleSystem.gravity);
  1341. if (parsedParticleSystem.noiseStrength) {
  1342. particleSystem.noiseStrength = Vector3.FromArray(parsedParticleSystem.noiseStrength);
  1343. }
  1344. particleSystem.color1 = Color4.FromArray(parsedParticleSystem.color1);
  1345. particleSystem.color2 = Color4.FromArray(parsedParticleSystem.color2);
  1346. particleSystem.colorDead = Color4.FromArray(parsedParticleSystem.colorDead);
  1347. particleSystem.updateSpeed = parsedParticleSystem.updateSpeed;
  1348. particleSystem.targetStopDuration = parsedParticleSystem.targetStopDuration;
  1349. particleSystem.blendMode = parsedParticleSystem.blendMode;
  1350. if (parsedParticleSystem.colorGradients) {
  1351. for (var colorGradient of parsedParticleSystem.colorGradients) {
  1352. particleSystem.addColorGradient(colorGradient.gradient, Color4.FromArray(colorGradient.color1), colorGradient.color2 ? Color4.FromArray(colorGradient.color2) : undefined);
  1353. }
  1354. }
  1355. if (parsedParticleSystem.sizeGradients) {
  1356. for (var sizeGradient of parsedParticleSystem.sizeGradients) {
  1357. particleSystem.addSizeGradient(sizeGradient.gradient, sizeGradient.factor1 !== undefined ? sizeGradient.factor1 : sizeGradient.factor, sizeGradient.factor2);
  1358. }
  1359. }
  1360. if (parsedParticleSystem.angularSpeedGradients) {
  1361. for (var angularSpeedGradient of parsedParticleSystem.angularSpeedGradients) {
  1362. particleSystem.addAngularSpeedGradient(angularSpeedGradient.gradient, angularSpeedGradient.factor1 !== undefined ? angularSpeedGradient.factor1 : angularSpeedGradient.factor, angularSpeedGradient.factor2);
  1363. }
  1364. }
  1365. if (parsedParticleSystem.velocityGradients) {
  1366. for (var velocityGradient of parsedParticleSystem.velocityGradients) {
  1367. particleSystem.addVelocityGradient(velocityGradient.gradient, velocityGradient.factor1 !== undefined ? velocityGradient.factor1 : velocityGradient.factor, velocityGradient.factor2);
  1368. }
  1369. }
  1370. if (parsedParticleSystem.limitVelocityGradients) {
  1371. for (var limitVelocityGradient of parsedParticleSystem.limitVelocityGradients) {
  1372. particleSystem.addLimitVelocityGradient(limitVelocityGradient.gradient, limitVelocityGradient.factor1 !== undefined ? limitVelocityGradient.factor1 : limitVelocityGradient.factor, limitVelocityGradient.factor2);
  1373. }
  1374. particleSystem.limitVelocityDamping = parsedParticleSystem.limitVelocityDamping;
  1375. }
  1376. if (parsedParticleSystem.noiseTexture) {
  1377. particleSystem.noiseTexture = ProceduralTexture.Parse(parsedParticleSystem.noiseTexture, scene, rootUrl);
  1378. }
  1379. // Emitter
  1380. let emitterType: IParticleEmitterType;
  1381. if (parsedParticleSystem.particleEmitterType) {
  1382. switch (parsedParticleSystem.particleEmitterType.type) {
  1383. case "SphereParticleEmitter":
  1384. emitterType = new SphereParticleEmitter();
  1385. break;
  1386. case "SphereDirectedParticleEmitter":
  1387. emitterType = new SphereDirectedParticleEmitter();
  1388. break;
  1389. case "ConeEmitter":
  1390. case "ConeParticleEmitter":
  1391. emitterType = new ConeParticleEmitter();
  1392. break;
  1393. case "BoxEmitter":
  1394. case "BoxParticleEmitter":
  1395. default:
  1396. emitterType = new BoxParticleEmitter();
  1397. break;
  1398. }
  1399. emitterType.parse(parsedParticleSystem.particleEmitterType);
  1400. } else {
  1401. emitterType = new BoxParticleEmitter();
  1402. emitterType.parse(parsedParticleSystem);
  1403. }
  1404. particleSystem.particleEmitterType = emitterType;
  1405. // Animation sheet
  1406. particleSystem.startSpriteCellID = parsedParticleSystem.startSpriteCellID;
  1407. particleSystem.endSpriteCellID = parsedParticleSystem.endSpriteCellID;
  1408. particleSystem.spriteCellWidth = parsedParticleSystem.spriteCellWidth;
  1409. particleSystem.spriteCellHeight = parsedParticleSystem.spriteCellHeight;
  1410. particleSystem.spriteCellChangeSpeed = parsedParticleSystem.spriteCellChangeSpeed;
  1411. }
  1412. /**
  1413. * Parses a JSON object to create a particle system.
  1414. * @param parsedParticleSystem The JSON object to parse
  1415. * @param scene The scene to create the particle system in
  1416. * @param rootUrl The root url to use to load external dependencies like texture
  1417. * @returns the Parsed particle system
  1418. */
  1419. public static Parse(parsedParticleSystem: any, scene: Scene, rootUrl: string): ParticleSystem {
  1420. var name = parsedParticleSystem.name;
  1421. var custom: Nullable<Effect> = null;
  1422. var program: any = null;
  1423. if (parsedParticleSystem.customShader) {
  1424. program = parsedParticleSystem.customShader;
  1425. var defines: string = (program.shaderOptions.defines.length > 0) ? program.shaderOptions.defines.join("\n") : "";
  1426. custom = scene.getEngine().createEffectForParticles(program.shaderPath.fragmentElement, program.shaderOptions.uniforms, program.shaderOptions.samplers, defines);
  1427. }
  1428. var particleSystem = new ParticleSystem(name, parsedParticleSystem.capacity, scene, custom, parsedParticleSystem.isAnimationSheetEnabled);
  1429. particleSystem.customShader = program;
  1430. if (parsedParticleSystem.id) {
  1431. particleSystem.id = parsedParticleSystem.id;
  1432. }
  1433. // Auto start
  1434. if (parsedParticleSystem.preventAutoStart) {
  1435. particleSystem.preventAutoStart = parsedParticleSystem.preventAutoStart;
  1436. }
  1437. ParticleSystem._Parse(parsedParticleSystem, particleSystem, scene, rootUrl);
  1438. particleSystem.textureMask = Color4.FromArray(parsedParticleSystem.textureMask);
  1439. if (!particleSystem.preventAutoStart) {
  1440. particleSystem.start();
  1441. }
  1442. return particleSystem;
  1443. }
  1444. }
  1445. }