Home Reference Source

src/emitter/Emitter.js

  1. import {
  2. DEFAULT_BIND_EMITTER,
  3. DEFAULT_BIND_EMITTER_EVENT,
  4. DEFAULT_DAMPING,
  5. DEFAULT_EMITTER_INDEX,
  6. DEFAULT_EMITTER_RATE,
  7. } from './constants';
  8. import EventDispatcher, {
  9. EMITTER_DEAD,
  10. PARTICLE_CREATED,
  11. PARTICLE_DEAD,
  12. PARTICLE_UPDATE,
  13. SYSTEM_UPDATE,
  14. } from '../events';
  15. import { INTEGRATION_TYPE_EULER, integrate } from '../math';
  16. import { Util, uid } from '../utils';
  17.  
  18. import { InitializerUtil } from '../initializer';
  19. import Particle from '../core/Particle';
  20. import isNumber from 'lodash/isNumber';
  21. import { EMITTER_TYPE_EMITTER as type } from './types';
  22.  
  23. /**
  24. * Emitters are the System engine's particle factories. They cause particles to
  25. * be rendered by emitting them, and store all particle initializers and behaviours.
  26. *
  27. */
  28. export default class Emitter extends Particle {
  29. /**
  30. * Constructs an Emitter instance.
  31. *
  32. * @param {object} properties - The properties to instantiate the emitter with
  33. * @return void
  34. */
  35. constructor(properties) {
  36. super(properties);
  37.  
  38. /**
  39. * @desc The class type.
  40. * @type {string}
  41. */
  42. this.type = type;
  43.  
  44. /**
  45. * @desc The particles emitted by this emitter.
  46. * @type {array}
  47. */
  48. this.particles = [];
  49.  
  50. /**
  51. * @desc The initializers for particles emitted by this emitter.
  52. * @type {array}
  53. */
  54. this.initializers = [];
  55.  
  56. /**
  57. * @desc The behaviours for particles emitted by this emitter.
  58. * @type {array}
  59. */
  60. this.behaviours = [];
  61.  
  62. /**
  63. * @desc The behaviours for the emitter.
  64. * @type {array}
  65. */
  66. this.emitterBehaviours = [];
  67.  
  68. /**
  69. * @desc The current emit iteration.
  70. * @type {integer}
  71. */
  72. this.currentEmitTime = 0;
  73.  
  74. /**
  75. * @desc The total number of times the emitter should emit particles.
  76. * @type {integer}
  77. */
  78. this.totalEmitTimes = -1;
  79.  
  80. /**
  81. * @desc The friction coefficient for all particle to emit by.
  82. * @type {number}
  83. */
  84. this.damping = DEFAULT_DAMPING;
  85.  
  86. /**
  87. * @desc Ensures that particles emitted by this emitter are positioned
  88. * according to the emitter's properties.
  89. * @type {boolean}
  90. */
  91. this.bindEmitter = DEFAULT_BIND_EMITTER;
  92.  
  93. /**
  94. * @desc Determines if the emitter will dispatch internal events. Defaults
  95. * to false
  96. * @type {boolean}
  97. */
  98. this.bindEmitterEvent = DEFAULT_BIND_EMITTER_EVENT;
  99.  
  100. /**
  101. * @desc The number of particles to emit per second (a [particle]/b [s])
  102. * @type {Rate}
  103. */
  104. this.rate = DEFAULT_EMITTER_RATE;
  105.  
  106. /**
  107. * @desc Determines if the emitter is emitting particles or not.
  108. * @type {boolean}
  109. */
  110. this.isEmitting = false;
  111.  
  112. /**
  113. * @desc The emitter's id.
  114. * @type {string}
  115. */
  116. this.id = `emitter-${uid()}`;
  117. this.cID = 0;
  118. this.name = 'Emitter';
  119.  
  120. /**
  121. * @desc The index of the emitter as it is added to the system.
  122. * @type {number|undefined}
  123. */
  124. this.index = DEFAULT_EMITTER_INDEX;
  125.  
  126. /**
  127. * @desc The emitter's internal event dispatcher.
  128. * @type {EventDispatcher}
  129. */
  130. this.eventDispatcher = new EventDispatcher();
  131. }
  132.  
  133. /**
  134. * Proxy method for the internal event dispatcher's dispatchEvent method.
  135. *
  136. * @param {string} event - The event to dispatch
  137. * @param {object<Particle>} [target=this] - The event target
  138. */
  139. dispatch(event, target = this) {
  140. this.eventDispatcher.dispatchEvent(event, target);
  141. }
  142.  
  143. /**
  144. * Sets the emitter rate.
  145. *
  146. * @param {Rate} rate - a rate initializer object
  147. * @return {Emitter}
  148. */
  149. setRate(rate) {
  150. this.rate = rate;
  151.  
  152. return this;
  153. }
  154.  
  155. /**
  156. * Sets the position of the emitter.
  157. *
  158. * @param {object} newPosition - an object the new x, y and z props
  159. * @return {Emitter}
  160. */
  161. setPosition(newPosition = {}) {
  162. const { position } = this;
  163. const { x = position.x, y = position.y, z = position.z } = newPosition;
  164.  
  165. this.position.set(x, y, z);
  166.  
  167. return this;
  168. }
  169.  
  170. /**
  171. * Sets the rotation of the emitter.
  172. *
  173. * @param {object} newRotation - an object the new x, y and z props
  174. * @return {Emitter}
  175. */
  176. setRotation(newRotation = {}) {
  177. const { rotation } = this;
  178. const { x = rotation.x, y = rotation.y, z = rotation.z } = newRotation;
  179.  
  180. this.rotation.set(x, y, z);
  181.  
  182. return this;
  183. }
  184.  
  185. /**
  186. * Sets the total number of times the emitter should emit particles as well as
  187. * the emitter's life. Also intializes the emitter rate.
  188. * This enables the emitter to emit particles.
  189. *
  190. * @param {number} [totalEmitTimes=Infinity] - the total number of times to emit particles
  191. * @param {number} [life=Infinity] - the life of this emitter in milliseconds
  192. * @return {Emitter}
  193. */
  194. emit(totalEmitTimes = Infinity, life = Infinity) {
  195. this.currentEmitTime = 0;
  196. this.totalEmitTimes = isNumber(totalEmitTimes) ? totalEmitTimes : Infinity;
  197.  
  198. if (totalEmitTimes === 1) {
  199. this.life = totalEmitTimes;
  200. } else {
  201. this.life = isNumber(life) ? life : Infinity;
  202. }
  203.  
  204. this.rate.init();
  205. this.isEmitting = true;
  206.  
  207. return this;
  208. }
  209.  
  210. /**
  211. * Experimental emit method that is designed to be called from the System.emit method.
  212. *
  213. * @return {Emitter}
  214. */
  215. experimental_emit() {
  216. const { isEmitting, totalEmitTimes, life } = this;
  217.  
  218. if (!isEmitting) {
  219. this.currentEmitTime = 0;
  220.  
  221. if (!totalEmitTimes) {
  222. this.setTotalEmitTimes(Infinity);
  223. }
  224.  
  225. if (!life) {
  226. this.setLife(Infinity);
  227. }
  228.  
  229. this.rate.init();
  230. this.isEmitting = true;
  231. }
  232.  
  233. return this;
  234. }
  235.  
  236. /**
  237. * Sets the total emit times for the emitter.
  238. *
  239. * @param {number} [totalEmitTimes=Infinity] - the total number of times to emit particles
  240. * @return {Emitter}
  241. */
  242. setTotalEmitTimes(totalEmitTimes = Infinity) {
  243. this.totalEmitTimes = isNumber(totalEmitTimes) ? totalEmitTimes : Infinity;
  244.  
  245. return this;
  246. }
  247.  
  248. /**
  249. * Sets the life of the emitter.
  250. *
  251. * @param {number} [life=Infinity] - the life of this emitter in milliseconds
  252. * @return {Emitter}
  253. */
  254. setLife(life = Infinity) {
  255. if (this.totalEmitTimes === 1) {
  256. this.life = this.totalEmitTimes;
  257. } else {
  258. this.life = isNumber(life) ? life : Infinity;
  259. }
  260.  
  261. return this;
  262. }
  263.  
  264. /**
  265. * Stops the emitter from emitting particles.
  266. *
  267. * @return void
  268. */
  269. stopEmit() {
  270. this.totalEmitTimes = -1;
  271. this.currentEmitTime = 0;
  272. this.isEmitting = false;
  273. }
  274.  
  275. /**
  276. * Kills all of the emitter's particles.
  277. *
  278. * @return void
  279. */
  280. removeAllParticles() {
  281. let i = this.particles.length;
  282.  
  283. while (i--) {
  284. this.particles[i].dead = true;
  285. }
  286. }
  287.  
  288. /**
  289. * Adds a particle initializer to the emitter.
  290. * Each initializer is run on each particle when they are created.
  291. *
  292. * @param {Initializer} initializer - The initializer to add
  293. * @return {Emitter}
  294. */
  295. addInitializer(initializer) {
  296. this.initializers.push(initializer);
  297.  
  298. return this;
  299. }
  300.  
  301. /**
  302. * Adds multiple particle initializers to the emitter.
  303. *
  304. * @param {array<Initializer>} initializers - an array of particle initializers
  305. * @return {Emitter}
  306. */
  307. addInitializers(initializers) {
  308. let i = initializers.length;
  309.  
  310. while (i--) {
  311. this.addInitializer(initializers[i]);
  312. }
  313.  
  314. return this;
  315. }
  316.  
  317. /**
  318. * Sets the emitter's particle initializers.
  319. *
  320. * @param {array<Initializer>} initializers - an array of particle initializers
  321. * @return {Emitter}
  322. */
  323. setInitializers(initializers) {
  324. this.initializers = initializers;
  325.  
  326. return this;
  327. }
  328.  
  329. /**
  330. * Removes an initializer from the emitter's initializers array.
  331. *
  332. * @param {Initializer} initializer - The initializer to remove
  333. * @return {Emitter}
  334. */
  335. removeInitializer(initializer) {
  336. const index = this.initializers.indexOf(initializer);
  337.  
  338. if (index > -1) {
  339. this.initializers.splice(index, 1);
  340. }
  341.  
  342. return this;
  343. }
  344.  
  345. /**
  346. * Removes all initializers.
  347. *
  348. * @return {Emitter}
  349. */
  350. removeAllInitializers() {
  351. Util.destroyArray(this.initializers);
  352.  
  353. return this;
  354. }
  355.  
  356. /**
  357. * Adds a behaviour to the emitter. All emitter behaviours are added to each particle when
  358. * they are emitted.
  359. *
  360. * @param {Behaviour} behaviour - The behaviour to add to the emitter
  361. * @return {Emitter}
  362. */
  363. addBehaviour(behaviour) {
  364. this.behaviours.push(behaviour);
  365.  
  366. return this;
  367. }
  368.  
  369. /**
  370. * Adds multiple behaviours to the emitter.
  371. *
  372. * @param {array<Behaviour>} behaviours - an array of emitter behaviours
  373. * @return {Emitter}
  374. */
  375. addBehaviours(behaviours) {
  376. let i = behaviours.length;
  377.  
  378. while (i--) {
  379. this.addBehaviour(behaviours[i]);
  380. }
  381.  
  382. return this;
  383. }
  384.  
  385. /**
  386. * Sets the emitter's behaviours.
  387. *
  388. * @param {array<Behaviour>} behaviours - an array of emitter behaviours
  389. * @return {Emitter}
  390. */
  391. setBehaviours(behaviours) {
  392. this.behaviours = behaviours;
  393.  
  394. return this;
  395. }
  396.  
  397. /**
  398. * Removes the behaviour from the emitter's behaviours array.
  399. *
  400. * @param {Behaviour} behaviour - The behaviour to remove
  401. * @return {Emitter}
  402. */
  403. removeBehaviour(behaviour) {
  404. const index = this.behaviours.indexOf(behaviour);
  405.  
  406. if (index > -1) {
  407. this.behaviours.splice(index, 1);
  408. }
  409.  
  410. return this;
  411. }
  412.  
  413. /**
  414. * Removes all behaviours from the emitter.
  415. *
  416. * @return {Emitter}
  417. */
  418. removeAllBehaviours() {
  419. Util.destroyArray(this.behaviours);
  420.  
  421. return this;
  422. }
  423.  
  424. /**
  425. * Adds an emitter behaviour to the emitter.
  426. *
  427. * @param {Behaviour} behaviour - The behaviour to add to the emitter
  428. * @return {Emitter}
  429. */
  430. addEmitterBehaviour(behaviour) {
  431. this.emitterBehaviours.push(behaviour);
  432.  
  433. behaviour.initialize(this);
  434.  
  435. return this;
  436. }
  437.  
  438. /**
  439. * Adds multiple behaviours to the emitter.
  440. *
  441. * @param {array<Behaviour>} behaviours - an array of emitter behaviours
  442. * @return {Emitter}
  443. */
  444. addEmitterBehaviours(behaviours) {
  445. let i = behaviours.length;
  446.  
  447. while (i--) {
  448. this.addEmitterBehaviour(behaviours[i]);
  449. }
  450.  
  451. return this;
  452. }
  453.  
  454. /**
  455. * Sets the emitter's behaviours.
  456. *
  457. * @param {array<Behaviour>} behaviours - an array of emitter behaviours
  458. * @return {Emitter}
  459. */
  460. setEmitterBehaviours(behaviours) {
  461. const length = behaviours.length;
  462.  
  463. this.emitterBehaviours = behaviours;
  464.  
  465. for (let i = 0; i < length; i++) {
  466. this.emitterBehaviours[i].initialize(this);
  467. }
  468.  
  469. return this;
  470. }
  471.  
  472. /**
  473. * Removes the behaviour from the emitter's behaviours array.
  474. *
  475. * @param {Behaviour} behaviour - The behaviour to remove
  476. * @return {Emitter}
  477. */
  478. removeEmitterBehaviour(behaviour) {
  479. const index = this.emitterBehaviours.indexOf(behaviour);
  480.  
  481. if (index > -1) {
  482. this.emitterBehaviours.splice(index, 1);
  483. }
  484.  
  485. return this;
  486. }
  487.  
  488. /**
  489. * Removes all behaviours from the emitter.
  490. *
  491. * @return {Emitter}
  492. */
  493. removeAllEmitterBehaviours() {
  494. Util.destroyArray(this.emitterBehaviours);
  495.  
  496. return this;
  497. }
  498.  
  499. /**
  500. * Adds the event listener for the EMITTER_DEAD event.
  501. *
  502. * @param {onEmitterDead} - The function to call when the EMITTER_DEAD is dispatched.
  503. * @return {Emitter}
  504. */
  505. addOnEmitterDeadEventListener(onEmitterDead) {
  506. this.eventDispatcher.addEventListener(`${this.id}_${EMITTER_DEAD}`, () =>
  507. onEmitterDead()
  508. );
  509.  
  510. return this;
  511. }
  512.  
  513. /**
  514. * Creates a particle by retreiving one from the pool and setting it up with
  515. * the supplied initializer and behaviour.
  516. *
  517. * @return {Emitter}
  518. */
  519. createParticle() {
  520. const particle = this.parent.pool.get(Particle);
  521. const index = this.particles.length;
  522.  
  523. this.setupParticle(particle, index);
  524. this.parent && this.parent.dispatch(PARTICLE_CREATED, particle);
  525. this.bindEmitterEvent && this.dispatch(PARTICLE_CREATED, particle);
  526.  
  527. return particle;
  528. }
  529.  
  530. /**
  531. * Sets up a particle by running all initializers on it and setting its behaviours.
  532. * Also adds the particle to this.particles.
  533. *
  534. * @param {Particle} particle - The particle to setup
  535. * @return void
  536. */
  537. setupParticle(particle, index) {
  538. const { initializers, behaviours } = this;
  539.  
  540. InitializerUtil.initialize(this, particle, initializers);
  541.  
  542. particle.addBehaviours(behaviours);
  543. particle.parent = this;
  544. particle.index = index;
  545.  
  546. this.particles.push(particle);
  547. }
  548.  
  549. /**
  550. * Updates the emitter according to the time passed by calling the generate
  551. * and integrate methods. The generate method creates particles, the integrate
  552. * method updates existing particles.
  553. *
  554. * If the emitter age is greater than time, the emitter is killed.
  555. *
  556. * This method also indexes/deindexes particles.
  557. *
  558. * @param {number} time - System engine time
  559. * @return void
  560. */
  561. update(time) {
  562. if (!this.isEmitting && this.particles.length === 0) {
  563. return;
  564. }
  565.  
  566. this.age += time;
  567.  
  568. if (this.dead || this.age >= this.life) {
  569. this.destroy();
  570. }
  571.  
  572. if (this.isEmitting)
  573. {
  574. this.generate(time);
  575. }
  576.  
  577. this.integrate(time);
  578.  
  579. let i = this.particles.length;
  580.  
  581. while (i--) {
  582. const particle = this.particles[i];
  583.  
  584. if (particle.dead) {
  585. this.parent && this.parent.dispatch(PARTICLE_DEAD, particle);
  586. this.bindEmitterEvent && this.dispatch(PARTICLE_DEAD, particle);
  587. this.parent.pool.expire(particle.reset());
  588. this.particles.splice(i, 1);
  589. if(this.particles.length === 0)
  590. {
  591. this.parent && this.parent.dispatch(SYSTEM_UPDATE);
  592. }
  593. }
  594. }
  595.  
  596. this.updateEmitterBehaviours(time);
  597. }
  598.  
  599. /**
  600. * Updates the emitter's emitter behaviours.
  601. *
  602. * @param {number} time - System engine time
  603. * @return void
  604. */
  605. updateEmitterBehaviours(time) {
  606. if (this.sleep) {
  607. return;
  608. }
  609.  
  610. const length = this.emitterBehaviours.length;
  611.  
  612. for (let i = 0; i < length; i++) {
  613. this.emitterBehaviours[i].applyBehaviour(this, time, i);
  614. }
  615. }
  616.  
  617. /**
  618. * Runs the integration algorithm on the emitter and all particles.
  619. * Updates the particles with the timstamp passed.
  620. *
  621. * @param {number} time - System engine time
  622. * @return void
  623. */
  624. integrate(time) {
  625. const integrationType = this.parent
  626. ? this.parent.integrationType
  627. : INTEGRATION_TYPE_EULER;
  628. const damping = 1 - this.damping;
  629.  
  630. integrate(this, time, damping, integrationType);
  631.  
  632. let index = this.particles.length;
  633.  
  634. while (index--) {
  635. const particle = this.particles[index];
  636.  
  637. particle.update(time, index);
  638. integrate(particle, time, damping, integrationType);
  639.  
  640. this.parent && this.parent.dispatch(PARTICLE_UPDATE, particle);
  641. this.bindEmitterEvent && this.dispatch(PARTICLE_UPDATE, particle);
  642. }
  643. }
  644.  
  645. /**
  646. * Generates new particles.
  647. *
  648. * @param {number} time - System engine time
  649. * @return void
  650. */
  651. generate(time) {
  652. if (this.totalEmitTimes === 1) {
  653. let i = this.rate.getValue(99999);
  654.  
  655. if (i > 0) {
  656. this.cID = i;
  657. }
  658.  
  659. while (i--) {
  660. this.createParticle();
  661. }
  662.  
  663. this.totalEmitTimes = 0;
  664.  
  665. return;
  666. }
  667.  
  668. this.currentEmitTime += time;
  669.  
  670. if (this.currentEmitTime < this.totalEmitTimes) {
  671. let i = this.rate.getValue(time);
  672.  
  673. if (i > 0) {
  674. this.cID = i;
  675. }
  676.  
  677. while (i--) {
  678. this.createParticle();
  679. }
  680. }
  681. }
  682.  
  683. /**
  684. * Kills the emitter.
  685. *
  686. * @return void
  687. */
  688. destroy() {
  689. this.dead = true;
  690. this.energy = 0;
  691. this.totalEmitTimes = -1;
  692.  
  693. if (this.particles.length == 0) {
  694. this.isEmitting = false;
  695. this.removeAllInitializers();
  696. this.removeAllBehaviours();
  697. this.dispatch(`${this.id}_${EMITTER_DEAD}`);
  698.  
  699. this.parent && this.parent.removeEmitter(this);
  700. }
  701. }
  702. }