babylon.particleSystem.ts 49 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204
  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 implements IDisposable, IAnimatable, IParticleSystem {
  9. /**
  10. * Source color is added to the destination color without alpha affecting the result.
  11. */
  12. public static BLENDMODE_ONEONE = 0;
  13. /**
  14. * Blend current color and particle color using particle’s alpha.
  15. */
  16. public static BLENDMODE_STANDARD = 1;
  17. /**
  18. * List of animations used by the particle system.
  19. */
  20. public animations: Animation[] = [];
  21. /**
  22. * The id of the Particle system.
  23. */
  24. public id: string;
  25. /**
  26. * The friendly name of the Particle system.
  27. */
  28. public name: string;
  29. /**
  30. * The rendering group used by the Particle system to chose when to render.
  31. */
  32. public renderingGroupId = 0;
  33. /**
  34. * The emitter represents the Mesh or position we are attaching the particle system to.
  35. */
  36. public emitter: Nullable<AbstractMesh | Vector3> = null;
  37. /**
  38. * The maximum number of particles to emit per frame
  39. */
  40. public emitRate = 10;
  41. /**
  42. * If you want to launch only a few particles at once, that can be done, as well.
  43. */
  44. public manualEmitCount = -1;
  45. /**
  46. * The overall motion speed (0.01 is default update speed, faster updates = faster animation)
  47. */
  48. public updateSpeed = 0.01;
  49. /**
  50. * The amount of time the particle system is running (depends of the overall update speed).
  51. */
  52. public targetStopDuration = 0;
  53. /**
  54. * Specifies whether the particle system will be disposed once it reaches the end of the animation.
  55. */
  56. public disposeOnStop = false;
  57. /**
  58. * Minimum power of emitting particles.
  59. */
  60. public minEmitPower = 1;
  61. /**
  62. * Maximum power of emitting particles.
  63. */
  64. public maxEmitPower = 1;
  65. /**
  66. * Minimum life time of emitting particles.
  67. */
  68. public minLifeTime = 1;
  69. /**
  70. * Maximum life time of emitting particles.
  71. */
  72. public maxLifeTime = 1;
  73. /**
  74. * Minimum Size of emitting particles.
  75. */
  76. public minSize = 1;
  77. /**
  78. * Maximum Size of emitting particles.
  79. */
  80. public maxSize = 1;
  81. /**
  82. * Minimum angular speed of emitting particles (Z-axis rotation for each particle).
  83. */
  84. public minAngularSpeed = 0;
  85. /**
  86. * Maximum angular speed of emitting particles (Z-axis rotation for each particle).
  87. */
  88. public maxAngularSpeed = 0;
  89. /**
  90. * The texture used to render each particle. (this can be a spritesheet)
  91. */
  92. public particleTexture: Nullable<Texture>;
  93. /**
  94. * The layer mask we are rendering the particles through.
  95. */
  96. public layerMask: number = 0x0FFFFFFF;
  97. /**
  98. * This can help using your own shader to render the particle system.
  99. * The according effect will be created
  100. */
  101. public customShader: any = null;
  102. /**
  103. * By default particle system starts as soon as they are created. This prevents the
  104. * automatic start to happen and let you decide when to start emitting particles.
  105. */
  106. public preventAutoStart: boolean = false;
  107. /**
  108. * This function can be defined to provide custom update for active particles.
  109. * This function will be called instead of regular update (age, position, color, etc.).
  110. * Do not forget that this function will be called on every frame so try to keep it simple and fast :)
  111. */
  112. public updateFunction: (particles: Particle[]) => void;
  113. /**
  114. * Callback triggered when the particle animation is ending.
  115. */
  116. public onAnimationEnd: Nullable<() => void> = null;
  117. /**
  118. * Blend mode use to render the particle, it can be either ParticleSystem.BLENDMODE_ONEONE or ParticleSystem.BLENDMODE_STANDARD.
  119. */
  120. public blendMode = ParticleSystem.BLENDMODE_ONEONE;
  121. /**
  122. * Forces the particle to write their depth information to the depth buffer. This can help preventing other draw calls
  123. * to override the particles.
  124. */
  125. public forceDepthWrite = false;
  126. /**
  127. * You can use gravity if you want to give an orientation to your particles.
  128. */
  129. public gravity = Vector3.Zero();
  130. /**
  131. * Random direction of each particle after it has been emitted, between direction1 and direction2 vectors.
  132. * This only works when particleEmitterTyps is a BoxParticleEmitter
  133. */
  134. public get direction1(): Vector3 {
  135. if ((<BoxParticleEmitter>this.particleEmitterType).direction1) {
  136. return (<BoxParticleEmitter>this.particleEmitterType).direction1;
  137. }
  138. return Vector3.Zero();
  139. }
  140. public set direction1(value: Vector3) {
  141. if ((<BoxParticleEmitter>this.particleEmitterType).direction1) {
  142. (<BoxParticleEmitter>this.particleEmitterType).direction1 = value;
  143. }
  144. }
  145. /**
  146. * Random direction of each particle after it has been emitted, between direction1 and direction2 vectors.
  147. * This only works when particleEmitterTyps is a BoxParticleEmitter
  148. */
  149. public get direction2(): Vector3 {
  150. if ((<BoxParticleEmitter>this.particleEmitterType).direction2) {
  151. return (<BoxParticleEmitter>this.particleEmitterType).direction2;
  152. }
  153. return Vector3.Zero();
  154. }
  155. public set direction2(value: Vector3) {
  156. if ((<BoxParticleEmitter>this.particleEmitterType).direction2) {
  157. (<BoxParticleEmitter>this.particleEmitterType).direction2 = value;
  158. }
  159. }
  160. /**
  161. * Minimum box point around our emitter. Our emitter is the center of particles source, but if you want your particles to emit from more than one point, then you can tell it to do so.
  162. * This only works when particleEmitterTyps is a BoxParticleEmitter
  163. */
  164. public get minEmitBox(): Vector3 {
  165. if ((<BoxParticleEmitter>this.particleEmitterType).minEmitBox) {
  166. return (<BoxParticleEmitter>this.particleEmitterType).minEmitBox;
  167. }
  168. return Vector3.Zero();
  169. }
  170. public set minEmitBox(value: Vector3) {
  171. if ((<BoxParticleEmitter>this.particleEmitterType).minEmitBox) {
  172. (<BoxParticleEmitter>this.particleEmitterType).minEmitBox = value;
  173. }
  174. }
  175. /**
  176. * Maximum box point around our emitter. Our emitter is the center of particles source, but if you want your particles to emit from more than one point, then you can tell it to do so.
  177. * This only works when particleEmitterTyps is a BoxParticleEmitter
  178. */
  179. public get maxEmitBox(): Vector3 {
  180. if ((<BoxParticleEmitter>this.particleEmitterType).maxEmitBox) {
  181. return (<BoxParticleEmitter>this.particleEmitterType).maxEmitBox;
  182. }
  183. return Vector3.Zero();
  184. }
  185. public set maxEmitBox(value: Vector3) {
  186. if ((<BoxParticleEmitter>this.particleEmitterType).maxEmitBox) {
  187. (<BoxParticleEmitter>this.particleEmitterType).maxEmitBox = value;
  188. }
  189. }
  190. /**
  191. * Random color of each particle after it has been emitted, between color1 and color2 vectors.
  192. */
  193. public color1 = new Color4(1.0, 1.0, 1.0, 1.0);
  194. /**
  195. * Random color of each particle after it has been emitted, between color1 and color2 vectors.
  196. */
  197. public color2 = new Color4(1.0, 1.0, 1.0, 1.0);
  198. /**
  199. * Color the particle will have at the end of its lifetime.
  200. */
  201. public colorDead = new Color4(0, 0, 0, 1.0);
  202. /**
  203. * An optional mask to filter some colors out of the texture, or filter a part of the alpha channel.
  204. */
  205. public textureMask = new Color4(1.0, 1.0, 1.0, 1.0);
  206. /**
  207. * The particle emitter type defines the emitter used by the particle system.
  208. * It can be for example box, sphere, or cone...
  209. */
  210. public particleEmitterType: IParticleEmitterType;
  211. /**
  212. * This function can be defined to specify initial direction for every new particle.
  213. * It by default use the emitterType defined function.
  214. */
  215. public startDirectionFunction: (emitPower: number, worldMatrix: Matrix, directionToUpdate: Vector3, particle: Particle) => void;
  216. /**
  217. * This function can be defined to specify initial position for every new particle.
  218. * It by default use the emitterType defined function.
  219. */
  220. public startPositionFunction: (worldMatrix: Matrix, positionToUpdate: Vector3, particle: Particle) => void;
  221. /**
  222. * If using a spritesheet (isAnimationSheetEnabled), defines if the sprite animation should loop between startSpriteCellID and endSpriteCellID or not.
  223. */
  224. public spriteCellLoop = true;
  225. /**
  226. * If using a spritesheet (isAnimationSheetEnabled) and spriteCellLoop defines the speed of the sprite loop.
  227. */
  228. public spriteCellChangeSpeed = 0;
  229. /**
  230. * If using a spritesheet (isAnimationSheetEnabled) and spriteCellLoop defines the first sprite cell to display.
  231. */
  232. public startSpriteCellID = 0;
  233. /**
  234. * If using a spritesheet (isAnimationSheetEnabled) and spriteCellLoop defines the last sprite cell to display.
  235. */
  236. public endSpriteCellID = 0;
  237. /**
  238. * If using a spritesheet (isAnimationSheetEnabled), defines the sprite cell width to use.
  239. */
  240. public spriteCellWidth = 0;
  241. /**
  242. * If using a spritesheet (isAnimationSheetEnabled), defines the sprite cell height to use.
  243. */
  244. public spriteCellHeight = 0;
  245. /**
  246. * An event triggered when the system is disposed.
  247. */
  248. public onDisposeObservable = new Observable<ParticleSystem>();
  249. private _onDisposeObserver: Nullable<Observer<ParticleSystem>>;
  250. /**
  251. * Sets a callback that will be triggered when the system is disposed.
  252. */
  253. public set onDispose(callback: () => void) {
  254. if (this._onDisposeObserver) {
  255. this.onDisposeObservable.remove(this._onDisposeObserver);
  256. }
  257. this._onDisposeObserver = this.onDisposeObservable.add(callback);
  258. }
  259. /**
  260. * Gets wether an animation sprite sheet is enabled or not on the particle system.
  261. */
  262. public get isAnimationSheetEnabled(): Boolean {
  263. return this._isAnimationSheetEnabled;
  264. }
  265. private _particles = new Array<Particle>();
  266. private _epsilon: number;
  267. private _capacity: number;
  268. private _scene: Scene;
  269. private _stockParticles = new Array<Particle>();
  270. private _newPartsExcess = 0;
  271. private _vertexData: Float32Array;
  272. private _vertexBuffer: Nullable<Buffer>;
  273. private _vertexBuffers: { [key: string]: VertexBuffer } = {};
  274. private _indexBuffer: Nullable<WebGLBuffer>;
  275. private _effect: Effect;
  276. private _customEffect: Nullable<Effect>;
  277. private _cachedDefines: string;
  278. private _scaledColorStep = new Color4(0, 0, 0, 0);
  279. private _colorDiff = new Color4(0, 0, 0, 0);
  280. private _scaledDirection = Vector3.Zero();
  281. private _scaledGravity = Vector3.Zero();
  282. private _currentRenderId = -1;
  283. private _alive: boolean;
  284. private _started = false;
  285. private _stopped = false;
  286. private _actualFrame = 0;
  287. private _scaledUpdateSpeed: number;
  288. private _vertexBufferSize = 11;
  289. private _isAnimationSheetEnabled: boolean;
  290. // end of sheet animation
  291. // Sub-emitters
  292. /**
  293. * 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.
  294. */
  295. public subEmitters: ParticleSystem[];
  296. /**
  297. * The current active Sub-systems, this property is used by the root particle system only.
  298. */
  299. public activeSubSystems: Array<ParticleSystem>;
  300. private _rootParticleSystem: ParticleSystem;
  301. //end of Sub-emitter
  302. /**
  303. * Gets the current list of active particles
  304. */
  305. public get particles(): Particle[] {
  306. return this._particles;
  307. }
  308. /**
  309. * Returns the string "ParticleSystem"
  310. * @returns a string containing the class name
  311. */
  312. public getClassName(): string {
  313. return "ParticleSystem";
  314. }
  315. /**
  316. * Instantiates a particle system.
  317. * 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.
  318. * @param name The name of the particle system
  319. * @param capacity The max number of particles alive at the same time
  320. * @param scene The scene the particle system belongs to
  321. * @param customEffect a custom effect used to change the way particles are rendered by default
  322. * @param isAnimationSheetEnabled Must be true if using a spritesheet to animate the particles texture
  323. * @param epsilon Offset used to render the particles
  324. */
  325. constructor(name: string, capacity: number, scene: Scene, customEffect: Nullable<Effect> = null, isAnimationSheetEnabled: boolean = false, epsilon: number = 0.01) {
  326. this.id = name;
  327. this.name = name;
  328. this._capacity = capacity;
  329. this._epsilon = epsilon;
  330. this._isAnimationSheetEnabled = isAnimationSheetEnabled;
  331. if (isAnimationSheetEnabled) {
  332. this._vertexBufferSize = 12;
  333. }
  334. this._scene = scene || Engine.LastCreatedScene;
  335. this._customEffect = customEffect;
  336. scene.particleSystems.push(this);
  337. this._createIndexBuffer();
  338. // 11 floats per particle (x, y, z, r, g, b, a, angle, size, offsetX, offsetY) + 1 filler
  339. this._vertexData = new Float32Array(capacity * this._vertexBufferSize * 4);
  340. this._vertexBuffer = new Buffer(scene.getEngine(), this._vertexData, true, this._vertexBufferSize);
  341. var positions = this._vertexBuffer.createVertexBuffer(VertexBuffer.PositionKind, 0, 3);
  342. var colors = this._vertexBuffer.createVertexBuffer(VertexBuffer.ColorKind, 3, 4);
  343. var options = this._vertexBuffer.createVertexBuffer("options", 7, 4);
  344. if (this._isAnimationSheetEnabled) {
  345. var cellIndexBuffer = this._vertexBuffer.createVertexBuffer("cellIndex", 11, 1);
  346. this._vertexBuffers["cellIndex"] = cellIndexBuffer;
  347. }
  348. this._vertexBuffers[VertexBuffer.PositionKind] = positions;
  349. this._vertexBuffers[VertexBuffer.ColorKind] = colors;
  350. this._vertexBuffers["options"] = options;
  351. // Default emitter type
  352. this.particleEmitterType = new BoxParticleEmitter();
  353. this.updateFunction = (particles: Particle[]): void => {
  354. for (var index = 0; index < particles.length; index++) {
  355. var particle = particles[index];
  356. particle.age += this._scaledUpdateSpeed;
  357. if (particle.age >= particle.lifeTime) { // Recycle by swapping with last particle
  358. this._emitFromParticle(particle);
  359. this.recycleParticle(particle);
  360. index--;
  361. continue;
  362. }
  363. else {
  364. particle.colorStep.scaleToRef(this._scaledUpdateSpeed, this._scaledColorStep);
  365. particle.color.addInPlace(this._scaledColorStep);
  366. if (particle.color.a < 0)
  367. particle.color.a = 0;
  368. particle.angle += particle.angularSpeed * this._scaledUpdateSpeed;
  369. particle.direction.scaleToRef(this._scaledUpdateSpeed, this._scaledDirection);
  370. particle.position.addInPlace(this._scaledDirection);
  371. this.gravity.scaleToRef(this._scaledUpdateSpeed, this._scaledGravity);
  372. particle.direction.addInPlace(this._scaledGravity);
  373. if (this._isAnimationSheetEnabled) {
  374. particle.updateCellIndex(this._scaledUpdateSpeed);
  375. }
  376. }
  377. }
  378. }
  379. }
  380. private _createIndexBuffer() {
  381. var indices = [];
  382. var index = 0;
  383. for (var count = 0; count < this._capacity; count++) {
  384. indices.push(index);
  385. indices.push(index + 1);
  386. indices.push(index + 2);
  387. indices.push(index);
  388. indices.push(index + 2);
  389. indices.push(index + 3);
  390. index += 4;
  391. }
  392. this._indexBuffer = this._scene.getEngine().createIndexBuffer(indices);
  393. }
  394. /**
  395. * Gets the maximum number of particles active at the same time.
  396. * @returns The max number of active particles.
  397. */
  398. public getCapacity(): number {
  399. return this._capacity;
  400. }
  401. /**
  402. * Gets Wether there are still active particles in the system.
  403. * @returns True if it is alive, otherwise false.
  404. */
  405. public isAlive(): boolean {
  406. return this._alive;
  407. }
  408. /**
  409. * Gets Wether the system has been started.
  410. * @returns True if it has been started, otherwise false.
  411. */
  412. public isStarted(): boolean {
  413. return this._started;
  414. }
  415. /**
  416. * Starts the particle system and begins to emit.
  417. */
  418. public start(): void {
  419. this._started = true;
  420. this._stopped = false;
  421. this._actualFrame = 0;
  422. if (this.subEmitters && this.subEmitters.length != 0) {
  423. this.activeSubSystems = new Array<ParticleSystem>();
  424. }
  425. }
  426. /**
  427. * Stops the particle system.
  428. * @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.
  429. */
  430. public stop(stopSubEmitters = true): void {
  431. this._stopped = true;
  432. if (stopSubEmitters) {
  433. this._stopSubEmitters();
  434. }
  435. }
  436. // animation sheet
  437. /**
  438. * Remove all active particles
  439. */
  440. public reset(): void {
  441. this._stockParticles = [];
  442. this._particles = [];
  443. }
  444. /**
  445. * @ignore (for internal use only)
  446. */
  447. public _appendParticleVertex(index: number, particle: Particle, offsetX: number, offsetY: number): void {
  448. var offset = index * this._vertexBufferSize;
  449. this._vertexData[offset] = particle.position.x;
  450. this._vertexData[offset + 1] = particle.position.y;
  451. this._vertexData[offset + 2] = particle.position.z;
  452. this._vertexData[offset + 3] = particle.color.r;
  453. this._vertexData[offset + 4] = particle.color.g;
  454. this._vertexData[offset + 5] = particle.color.b;
  455. this._vertexData[offset + 6] = particle.color.a;
  456. this._vertexData[offset + 7] = particle.angle;
  457. this._vertexData[offset + 8] = particle.size;
  458. this._vertexData[offset + 9] = offsetX;
  459. this._vertexData[offset + 10] = offsetY;
  460. }
  461. /**
  462. * @ignore (for internal use only)
  463. */
  464. public _appendParticleVertexWithAnimation(index: number, particle: Particle, offsetX: number, offsetY: number): void {
  465. if (offsetX === 0)
  466. offsetX = this._epsilon;
  467. else if (offsetX === 1)
  468. offsetX = 1 - this._epsilon;
  469. if (offsetY === 0)
  470. offsetY = this._epsilon;
  471. else if (offsetY === 1)
  472. offsetY = 1 - this._epsilon;
  473. var offset = index * this._vertexBufferSize;
  474. this._vertexData[offset] = particle.position.x;
  475. this._vertexData[offset + 1] = particle.position.y;
  476. this._vertexData[offset + 2] = particle.position.z;
  477. this._vertexData[offset + 3] = particle.color.r;
  478. this._vertexData[offset + 4] = particle.color.g;
  479. this._vertexData[offset + 5] = particle.color.b;
  480. this._vertexData[offset + 6] = particle.color.a;
  481. this._vertexData[offset + 7] = particle.angle;
  482. this._vertexData[offset + 8] = particle.size;
  483. this._vertexData[offset + 9] = offsetX;
  484. this._vertexData[offset + 10] = offsetY;
  485. this._vertexData[offset + 11] = particle.cellIndex;
  486. }
  487. // start of sub system methods
  488. /**
  489. * "Recycles" one of the particle by copying it back to the "stock" of particles and removing it from the active list.
  490. * Its lifetime will start back at 0.
  491. */
  492. public recycleParticle: (particle: Particle) => void = (particle) => {
  493. var lastParticle = <Particle>this._particles.pop();
  494. if (lastParticle !== particle) {
  495. lastParticle.copyTo(particle);
  496. }
  497. this._stockParticles.push(lastParticle);
  498. };
  499. private _stopSubEmitters(): void {
  500. if (!this.activeSubSystems) {
  501. return;
  502. }
  503. this.activeSubSystems.forEach(subSystem => {
  504. subSystem.stop(true);
  505. });
  506. this.activeSubSystems = new Array<ParticleSystem>();
  507. }
  508. private _createParticle: () => Particle = () => {
  509. var particle: Particle;
  510. if (this._stockParticles.length !== 0) {
  511. particle = <Particle>this._stockParticles.pop();
  512. particle.age = 0;
  513. particle.cellIndex = this.startSpriteCellID;
  514. } else {
  515. particle = new Particle(this);
  516. }
  517. return particle;
  518. }
  519. private _removeFromRoot(): void {
  520. if (!this._rootParticleSystem){
  521. return;
  522. }
  523. let index = this._rootParticleSystem.activeSubSystems.indexOf(this);
  524. if (index !== -1) {
  525. this._rootParticleSystem.activeSubSystems.splice(index, 1);
  526. }
  527. }
  528. private _emitFromParticle: (particle: Particle) => void = (particle) => {
  529. if (!this.subEmitters || this.subEmitters.length === 0) {
  530. return;
  531. }
  532. var templateIndex = Math.floor(Math.random() * this.subEmitters.length);
  533. var subSystem = this.subEmitters[templateIndex].clone(this.name + "_sub", particle.position.clone());
  534. subSystem._rootParticleSystem = this;
  535. this.activeSubSystems.push(subSystem);
  536. subSystem.start();
  537. }
  538. // end of sub system methods
  539. private _update(newParticles: number): void {
  540. // Update current
  541. this._alive = this._particles.length > 0;
  542. this.updateFunction(this._particles);
  543. // Add new ones
  544. var worldMatrix;
  545. if ((<AbstractMesh>this.emitter).position) {
  546. var emitterMesh = (<AbstractMesh>this.emitter);
  547. worldMatrix = emitterMesh.getWorldMatrix();
  548. } else {
  549. var emitterPosition = (<Vector3>this.emitter);
  550. worldMatrix = Matrix.Translation(emitterPosition.x, emitterPosition.y, emitterPosition.z);
  551. }
  552. var particle: Particle;
  553. for (var index = 0; index < newParticles; index++) {
  554. if (this._particles.length === this._capacity) {
  555. break;
  556. }
  557. particle = this._createParticle();
  558. this._particles.push(particle);
  559. var emitPower = Scalar.RandomRange(this.minEmitPower, this.maxEmitPower);
  560. if (this.startPositionFunction) {
  561. this.startPositionFunction(worldMatrix, particle.position, particle);
  562. }
  563. else {
  564. this.particleEmitterType.startPositionFunction(worldMatrix, particle.position, particle);
  565. }
  566. if (this.startDirectionFunction) {
  567. this.startDirectionFunction(emitPower, worldMatrix, particle.direction, particle);
  568. }
  569. else {
  570. this.particleEmitterType.startDirectionFunction(emitPower, worldMatrix, particle.direction, particle);
  571. }
  572. particle.lifeTime = Scalar.RandomRange(this.minLifeTime, this.maxLifeTime);
  573. particle.size = Scalar.RandomRange(this.minSize, this.maxSize);
  574. particle.angularSpeed = Scalar.RandomRange(this.minAngularSpeed, this.maxAngularSpeed);
  575. var step = Scalar.RandomRange(0, 1.0);
  576. Color4.LerpToRef(this.color1, this.color2, step, particle.color);
  577. this.colorDead.subtractToRef(particle.color, this._colorDiff);
  578. this._colorDiff.scaleToRef(1.0 / particle.lifeTime, particle.colorStep);
  579. }
  580. }
  581. private _getEffect(): Effect {
  582. if (this._customEffect) {
  583. return this._customEffect;
  584. };
  585. var defines = [];
  586. if (this._scene.clipPlane) {
  587. defines.push("#define CLIPPLANE");
  588. }
  589. if (this._isAnimationSheetEnabled) {
  590. defines.push("#define ANIMATESHEET");
  591. }
  592. // Effect
  593. var join = defines.join("\n");
  594. if (this._cachedDefines !== join) {
  595. this._cachedDefines = join;
  596. var attributesNamesOrOptions: any;
  597. var effectCreationOption: any;
  598. if (this._isAnimationSheetEnabled) {
  599. attributesNamesOrOptions = [VertexBuffer.PositionKind, VertexBuffer.ColorKind, "options", "cellIndex"];
  600. effectCreationOption = ["invView", "view", "projection", "particlesInfos", "vClipPlane", "textureMask"];
  601. }
  602. else {
  603. attributesNamesOrOptions = [VertexBuffer.PositionKind, VertexBuffer.ColorKind, "options"];
  604. effectCreationOption = ["invView", "view", "projection", "vClipPlane", "textureMask"]
  605. }
  606. this._effect = this._scene.getEngine().createEffect(
  607. "particles",
  608. attributesNamesOrOptions,
  609. effectCreationOption,
  610. ["diffuseSampler"], join);
  611. }
  612. return this._effect;
  613. }
  614. /**
  615. * Animates the particle system for the current frame by emitting new particles and or animating the living ones.
  616. */
  617. public animate(): void {
  618. if (!this._started)
  619. return;
  620. var effect = this._getEffect();
  621. // Check
  622. if (!this.emitter || !effect.isReady() || !this.particleTexture || !this.particleTexture.isReady())
  623. return;
  624. if (this._currentRenderId === this._scene.getRenderId()) {
  625. return;
  626. }
  627. this._currentRenderId = this._scene.getRenderId();
  628. this._scaledUpdateSpeed = this.updateSpeed * this._scene.getAnimationRatio();
  629. // determine the number of particles we need to create
  630. var newParticles;
  631. if (this.manualEmitCount > -1) {
  632. newParticles = this.manualEmitCount;
  633. this._newPartsExcess = 0;
  634. this.manualEmitCount = 0;
  635. } else {
  636. newParticles = ((this.emitRate * this._scaledUpdateSpeed) >> 0);
  637. this._newPartsExcess += this.emitRate * this._scaledUpdateSpeed - newParticles;
  638. }
  639. if (this._newPartsExcess > 1.0) {
  640. newParticles += this._newPartsExcess >> 0;
  641. this._newPartsExcess -= this._newPartsExcess >> 0;
  642. }
  643. this._alive = false;
  644. if (!this._stopped) {
  645. this._actualFrame += this._scaledUpdateSpeed;
  646. if (this.targetStopDuration && this._actualFrame >= this.targetStopDuration)
  647. this.stop();
  648. } else {
  649. newParticles = 0;
  650. }
  651. this._update(newParticles);
  652. // Stopped?
  653. if (this._stopped) {
  654. if (!this._alive) {
  655. this._started = false;
  656. if (this.onAnimationEnd) {
  657. this.onAnimationEnd();
  658. }
  659. if (this.disposeOnStop) {
  660. this._scene._toBeDisposed.push(this);
  661. }
  662. }
  663. }
  664. // Animation sheet
  665. if (this._isAnimationSheetEnabled) {
  666. this._appendParticleVertexes = this._appenedParticleVertexesWithSheet;
  667. }
  668. else {
  669. this._appendParticleVertexes = this._appenedParticleVertexesNoSheet;
  670. }
  671. // Update VBO
  672. var offset = 0;
  673. for (var index = 0; index < this._particles.length; index++) {
  674. var particle = this._particles[index];
  675. this._appendParticleVertexes(offset, particle);
  676. offset += 4;
  677. }
  678. if (this._vertexBuffer) {
  679. this._vertexBuffer.update(this._vertexData);
  680. }
  681. if (this.manualEmitCount === 0 && this.disposeOnStop) {
  682. this.stop();
  683. }
  684. }
  685. private _appendParticleVertexes: Nullable<(offset: number, particle: Particle) => void> = null;
  686. private _appenedParticleVertexesWithSheet(offset: number, particle: Particle) {
  687. this._appendParticleVertexWithAnimation(offset++, particle, 0, 0);
  688. this._appendParticleVertexWithAnimation(offset++, particle, 1, 0);
  689. this._appendParticleVertexWithAnimation(offset++, particle, 1, 1);
  690. this._appendParticleVertexWithAnimation(offset++, particle, 0, 1);
  691. }
  692. private _appenedParticleVertexesNoSheet(offset: number, particle: Particle) {
  693. this._appendParticleVertex(offset++, particle, 0, 0);
  694. this._appendParticleVertex(offset++, particle, 1, 0);
  695. this._appendParticleVertex(offset++, particle, 1, 1);
  696. this._appendParticleVertex(offset++, particle, 0, 1);
  697. }
  698. /**
  699. * Rebuilds the particle system.
  700. */
  701. public rebuild(): void {
  702. this._createIndexBuffer();
  703. if (this._vertexBuffer) {
  704. this._vertexBuffer._rebuild();
  705. }
  706. }
  707. /**
  708. * Is this system ready to be used/rendered
  709. * @return true if the system is ready
  710. */
  711. public isReady(): boolean {
  712. var effect = this._getEffect();
  713. if (!this.emitter || !effect.isReady() || !this.particleTexture || !this.particleTexture.isReady()) {
  714. return false;
  715. }
  716. return true;
  717. }
  718. /**
  719. * Renders the particle system in its current state.
  720. * @returns the current number of particles
  721. */
  722. public render(): number {
  723. var effect = this._getEffect();
  724. // Check
  725. if (!this.isReady() || !this._particles.length) {
  726. return 0;
  727. }
  728. var engine = this._scene.getEngine();
  729. // Render
  730. engine.enableEffect(effect);
  731. engine.setState(false);
  732. var viewMatrix = this._scene.getViewMatrix();
  733. effect.setTexture("diffuseSampler", this.particleTexture);
  734. effect.setMatrix("view", viewMatrix);
  735. effect.setMatrix("projection", this._scene.getProjectionMatrix());
  736. if (this._isAnimationSheetEnabled && this.particleTexture) {
  737. var baseSize = this.particleTexture.getBaseSize();
  738. effect.setFloat3("particlesInfos", this.spriteCellWidth / baseSize.width, this.spriteCellHeight / baseSize.height, baseSize.width / this.spriteCellWidth);
  739. }
  740. effect.setFloat4("textureMask", this.textureMask.r, this.textureMask.g, this.textureMask.b, this.textureMask.a);
  741. if (this._scene.clipPlane) {
  742. var clipPlane = this._scene.clipPlane;
  743. var invView = viewMatrix.clone();
  744. invView.invert();
  745. effect.setMatrix("invView", invView);
  746. effect.setFloat4("vClipPlane", clipPlane.normal.x, clipPlane.normal.y, clipPlane.normal.z, clipPlane.d);
  747. }
  748. // VBOs
  749. engine.bindBuffers(this._vertexBuffers, this._indexBuffer, effect);
  750. // Draw order
  751. if (this.blendMode === ParticleSystem.BLENDMODE_ONEONE) {
  752. engine.setAlphaMode(Engine.ALPHA_ONEONE);
  753. } else {
  754. engine.setAlphaMode(Engine.ALPHA_COMBINE);
  755. }
  756. if (this.forceDepthWrite) {
  757. engine.setDepthWrite(true);
  758. }
  759. engine.drawElementsType(Material.TriangleFillMode, 0, this._particles.length * 6);
  760. engine.setAlphaMode(Engine.ALPHA_DISABLE);
  761. return this._particles.length;
  762. }
  763. /**
  764. * Disposes the particle system and free the associated resources
  765. * @param disposeTexture defines if the particule texture must be disposed as well (true by default)
  766. */
  767. public dispose(disposeTexture = true): void {
  768. if (this._vertexBuffer) {
  769. this._vertexBuffer.dispose();
  770. this._vertexBuffer = null;
  771. }
  772. if (this._indexBuffer) {
  773. this._scene.getEngine()._releaseBuffer(this._indexBuffer);
  774. this._indexBuffer = null;
  775. }
  776. if (disposeTexture && this.particleTexture) {
  777. this.particleTexture.dispose();
  778. this.particleTexture = null;
  779. }
  780. this._removeFromRoot();
  781. // Remove from scene
  782. var index = this._scene.particleSystems.indexOf(this);
  783. if (index > -1) {
  784. this._scene.particleSystems.splice(index, 1);
  785. }
  786. // Callback
  787. this.onDisposeObservable.notifyObservers(this);
  788. this.onDisposeObservable.clear();
  789. }
  790. /**
  791. * Creates a Sphere Emitter for the particle system. (emits along the sphere radius)
  792. * @param radius The radius of the sphere to emit from
  793. * @returns the emitter
  794. */
  795. public createSphereEmitter(radius = 1): SphereParticleEmitter {
  796. var particleEmitter = new SphereParticleEmitter(radius);
  797. this.particleEmitterType = particleEmitter;
  798. return particleEmitter;
  799. }
  800. /**
  801. * Creates a Directed Sphere Emitter for the particle system. (emits between direction1 and direction2)
  802. * @param radius The radius of the sphere to emit from
  803. * @param direction1 Particles are emitted between the direction1 and direction2 from within the sphere
  804. * @param direction2 Particles are emitted between the direction1 and direction2 from within the sphere
  805. * @returns the emitter
  806. */
  807. public createDirectedSphereEmitter(radius = 1, direction1 = new Vector3(0, 1.0, 0), direction2 = new Vector3(0, 1.0, 0)): SphereDirectedParticleEmitter {
  808. var particleEmitter = new SphereDirectedParticleEmitter(radius, direction1, direction2)
  809. this.particleEmitterType = particleEmitter;
  810. return particleEmitter;
  811. }
  812. /**
  813. * Creates a Cone Emitter for the particle system. (emits from the cone to the particle position)
  814. * @param radius The radius of the cone to emit from
  815. * @param angle The base angle of the cone
  816. * @returns the emitter
  817. */
  818. public createConeEmitter(radius = 1, angle = Math.PI / 4): ConeParticleEmitter {
  819. var particleEmitter = new ConeParticleEmitter(radius, angle);
  820. this.particleEmitterType = particleEmitter;
  821. return particleEmitter;
  822. }
  823. // this method needs to be changed when breaking changes will be allowed to match the sphere and cone methods and properties direction1,2 and minEmitBox,maxEmitBox to be removed from the system.
  824. /**
  825. * Creates a Box Emitter for the particle system. (emits between direction1 and direction2 from withing the box defined by minEmitBox and maxEmitBox)
  826. * @param direction1 Particles are emitted between the direction1 and direction2 from within the box
  827. * @param direction2 Particles are emitted between the direction1 and direction2 from within the box
  828. * @param minEmitBox Particles are emitted from the box between minEmitBox and maxEmitBox
  829. * @param maxEmitBox Particles are emitted from the box between minEmitBox and maxEmitBox
  830. * @returns the emitter
  831. */
  832. public createBoxEmitter(direction1: Vector3, direction2: Vector3, minEmitBox: Vector3, maxEmitBox: Vector3): BoxParticleEmitter {
  833. var particleEmitter = new BoxParticleEmitter();
  834. this.particleEmitterType = particleEmitter;
  835. this.direction1 = direction1;
  836. this.direction2 = direction2;
  837. this.minEmitBox = minEmitBox;
  838. this.maxEmitBox = maxEmitBox;
  839. return particleEmitter;
  840. }
  841. // Clone
  842. /**
  843. * Clones the particle system.
  844. * @param name The name of the cloned object
  845. * @param newEmitter The new emitter to use
  846. * @returns the cloned particle system
  847. */
  848. public clone(name: string, newEmitter: any): ParticleSystem {
  849. var custom: Nullable<Effect> = null;
  850. var program: any = null;
  851. if (this.customShader != null) {
  852. program = this.customShader;
  853. var defines: string = (program.shaderOptions.defines.length > 0) ? program.shaderOptions.defines.join("\n") : "";
  854. custom = this._scene.getEngine().createEffectForParticles(program.shaderPath.fragmentElement, program.shaderOptions.uniforms, program.shaderOptions.samplers, defines);
  855. }
  856. var result = new ParticleSystem(name, this._capacity, this._scene, custom);
  857. result.customShader = program;
  858. Tools.DeepCopy(this, result, ["particles", "customShader"]);
  859. if (newEmitter === undefined) {
  860. newEmitter = this.emitter;
  861. }
  862. result.emitter = newEmitter;
  863. if (this.particleTexture) {
  864. result.particleTexture = new Texture(this.particleTexture.url, this._scene);
  865. }
  866. if (!this.preventAutoStart) {
  867. result.start();
  868. }
  869. return result;
  870. }
  871. /**
  872. * Serializes the particle system to a JSON object.
  873. * @returns the JSON object
  874. */
  875. public serialize(): any {
  876. var serializationObject: any = {};
  877. serializationObject.name = this.name;
  878. serializationObject.id = this.id;
  879. // Emitter
  880. if ((<AbstractMesh>this.emitter).position) {
  881. var emitterMesh = (<AbstractMesh>this.emitter);
  882. serializationObject.emitterId = emitterMesh.id;
  883. } else {
  884. var emitterPosition = (<Vector3>this.emitter);
  885. serializationObject.emitter = emitterPosition.asArray();
  886. }
  887. serializationObject.capacity = this.getCapacity();
  888. if (this.particleTexture) {
  889. serializationObject.textureName = this.particleTexture.name;
  890. }
  891. // Animations
  892. Animation.AppendSerializedAnimations(this, serializationObject);
  893. // Particle system
  894. serializationObject.minAngularSpeed = this.minAngularSpeed;
  895. serializationObject.maxAngularSpeed = this.maxAngularSpeed;
  896. serializationObject.minSize = this.minSize;
  897. serializationObject.maxSize = this.maxSize;
  898. serializationObject.minEmitPower = this.minEmitPower;
  899. serializationObject.maxEmitPower = this.maxEmitPower;
  900. serializationObject.minLifeTime = this.minLifeTime;
  901. serializationObject.maxLifeTime = this.maxLifeTime;
  902. serializationObject.emitRate = this.emitRate;
  903. serializationObject.minEmitBox = this.minEmitBox.asArray();
  904. serializationObject.maxEmitBox = this.maxEmitBox.asArray();
  905. serializationObject.gravity = this.gravity.asArray();
  906. serializationObject.direction1 = this.direction1.asArray();
  907. serializationObject.direction2 = this.direction2.asArray();
  908. serializationObject.color1 = this.color1.asArray();
  909. serializationObject.color2 = this.color2.asArray();
  910. serializationObject.colorDead = this.colorDead.asArray();
  911. serializationObject.updateSpeed = this.updateSpeed;
  912. serializationObject.targetStopDuration = this.targetStopDuration;
  913. serializationObject.textureMask = this.textureMask.asArray();
  914. serializationObject.blendMode = this.blendMode;
  915. serializationObject.customShader = this.customShader;
  916. serializationObject.preventAutoStart = this.preventAutoStart;
  917. serializationObject.startSpriteCellID = this.startSpriteCellID;
  918. serializationObject.endSpriteCellID = this.endSpriteCellID;
  919. serializationObject.spriteCellLoop = this.spriteCellLoop;
  920. serializationObject.spriteCellChangeSpeed = this.spriteCellChangeSpeed;
  921. serializationObject.spriteCellWidth = this.spriteCellWidth;
  922. serializationObject.spriteCellHeight = this.spriteCellHeight;
  923. serializationObject.isAnimationSheetEnabled = this._isAnimationSheetEnabled;
  924. // Emitter
  925. if (this.particleEmitterType) {
  926. serializationObject.particleEmitterType = this.particleEmitterType.serialize();
  927. }
  928. return serializationObject;
  929. }
  930. /**
  931. * Parses a JSON object to create a particle system.
  932. * @param parsedParticleSystem The JSON object to parse
  933. * @param scene The scene to create the particle system in
  934. * @param rootUrl The root url to use to load external dependencies like texture
  935. * @returns the Parsed particle system
  936. */
  937. public static Parse(parsedParticleSystem: any, scene: Scene, rootUrl: string): ParticleSystem {
  938. var name = parsedParticleSystem.name;
  939. var custom: Nullable<Effect> = null;
  940. var program: any = null;
  941. if (parsedParticleSystem.customShader) {
  942. program = parsedParticleSystem.customShader;
  943. var defines: string = (program.shaderOptions.defines.length > 0) ? program.shaderOptions.defines.join("\n") : "";
  944. custom = scene.getEngine().createEffectForParticles(program.shaderPath.fragmentElement, program.shaderOptions.uniforms, program.shaderOptions.samplers, defines);
  945. }
  946. var particleSystem = new ParticleSystem(name, parsedParticleSystem.capacity, scene, custom, parsedParticleSystem.isAnimationSheetEnabled);
  947. particleSystem.customShader = program;
  948. if (parsedParticleSystem.id) {
  949. particleSystem.id = parsedParticleSystem.id;
  950. }
  951. // Auto start
  952. if (parsedParticleSystem.preventAutoStart) {
  953. particleSystem.preventAutoStart = parsedParticleSystem.preventAutoStart;
  954. }
  955. // Texture
  956. if (parsedParticleSystem.textureName) {
  957. particleSystem.particleTexture = new Texture(rootUrl + parsedParticleSystem.textureName, scene);
  958. particleSystem.particleTexture.name = parsedParticleSystem.textureName;
  959. }
  960. // Emitter
  961. if (parsedParticleSystem.emitterId) {
  962. particleSystem.emitter = scene.getLastMeshByID(parsedParticleSystem.emitterId);
  963. } else {
  964. particleSystem.emitter = Vector3.FromArray(parsedParticleSystem.emitter);
  965. }
  966. // Animations
  967. if (parsedParticleSystem.animations) {
  968. for (var animationIndex = 0; animationIndex < parsedParticleSystem.animations.length; animationIndex++) {
  969. var parsedAnimation = parsedParticleSystem.animations[animationIndex];
  970. particleSystem.animations.push(Animation.Parse(parsedAnimation));
  971. }
  972. }
  973. if (parsedParticleSystem.autoAnimate) {
  974. scene.beginAnimation(particleSystem, parsedParticleSystem.autoAnimateFrom, parsedParticleSystem.autoAnimateTo, parsedParticleSystem.autoAnimateLoop, parsedParticleSystem.autoAnimateSpeed || 1.0);
  975. }
  976. // Particle system
  977. particleSystem.minAngularSpeed = parsedParticleSystem.minAngularSpeed;
  978. particleSystem.maxAngularSpeed = parsedParticleSystem.maxAngularSpeed;
  979. particleSystem.minSize = parsedParticleSystem.minSize;
  980. particleSystem.maxSize = parsedParticleSystem.maxSize;
  981. particleSystem.minLifeTime = parsedParticleSystem.minLifeTime;
  982. particleSystem.maxLifeTime = parsedParticleSystem.maxLifeTime;
  983. particleSystem.minEmitPower = parsedParticleSystem.minEmitPower;
  984. particleSystem.maxEmitPower = parsedParticleSystem.maxEmitPower;
  985. particleSystem.emitRate = parsedParticleSystem.emitRate;
  986. particleSystem.minEmitBox = Vector3.FromArray(parsedParticleSystem.minEmitBox);
  987. particleSystem.maxEmitBox = Vector3.FromArray(parsedParticleSystem.maxEmitBox);
  988. particleSystem.gravity = Vector3.FromArray(parsedParticleSystem.gravity);
  989. particleSystem.direction1 = Vector3.FromArray(parsedParticleSystem.direction1);
  990. particleSystem.direction2 = Vector3.FromArray(parsedParticleSystem.direction2);
  991. particleSystem.color1 = Color4.FromArray(parsedParticleSystem.color1);
  992. particleSystem.color2 = Color4.FromArray(parsedParticleSystem.color2);
  993. particleSystem.colorDead = Color4.FromArray(parsedParticleSystem.colorDead);
  994. particleSystem.updateSpeed = parsedParticleSystem.updateSpeed;
  995. particleSystem.targetStopDuration = parsedParticleSystem.targetStopDuration;
  996. particleSystem.textureMask = Color4.FromArray(parsedParticleSystem.textureMask);
  997. particleSystem.blendMode = parsedParticleSystem.blendMode;
  998. particleSystem.startSpriteCellID = parsedParticleSystem.startSpriteCellID;
  999. particleSystem.endSpriteCellID = parsedParticleSystem.endSpriteCellID;
  1000. particleSystem.spriteCellLoop = parsedParticleSystem.spriteCellLoop;
  1001. particleSystem.spriteCellChangeSpeed = parsedParticleSystem.spriteCellChangeSpeed;
  1002. particleSystem.spriteCellWidth = parsedParticleSystem.spriteCellWidth;
  1003. particleSystem.spriteCellHeight = parsedParticleSystem.spriteCellHeight;
  1004. if (!particleSystem.preventAutoStart) {
  1005. particleSystem.start();
  1006. }
  1007. return particleSystem;
  1008. }
  1009. }
  1010. }