Source: particles/ParticleContainer.js

particles/ParticleContainer.js

  1. import * as core from '../core';
  2. import { hex2rgb } from '../core/utils';
  3. /**
  4. * The ParticleContainer class is a really fast version of the Container built solely for speed,
  5. * so use when you need a lot of sprites or particles. The tradeoff of the ParticleContainer is that most advanced
  6. * functionality will not work. ParticleContainer implements the basic object transform (position, scale, rotation)
  7. * and some advanced functionality like tint (as of v4.5.6).
  8. * Other more advanced functionality like masking, children, filters, etc will not work on sprites in this batch.
  9. *
  10. * It's extremely easy to use :
  11. *
  12. * ```js
  13. * let container = new ParticleContainer();
  14. *
  15. * for (let i = 0; i < 100; ++i)
  16. * {
  17. * let sprite = new PIXI.Sprite.fromImage("myImage.png");
  18. * container.addChild(sprite);
  19. * }
  20. * ```
  21. *
  22. * And here you have a hundred sprites that will be rendered at the speed of light.
  23. *
  24. * @class
  25. * @extends PIXI.Container
  26. * @memberof PIXI.particles
  27. */
  28. export default class ParticleContainer extends core.Container
  29. {
  30. /**
  31. * @param {number} [maxSize=1500] - The maximum number of particles that can be rendered by the container.
  32. * Affects size of allocated buffers.
  33. * @param {object} [properties] - The properties of children that should be uploaded to the gpu and applied.
  34. * @param {boolean} [properties.vertices=false] - When true, vertices be uploaded and applied.
  35. * if sprite's ` scale/anchor/trim/frame/orig` is dynamic, please set `true`.
  36. * @param {boolean} [properties.position=true] - When true, position be uploaded and applied.
  37. * @param {boolean} [properties.rotation=false] - When true, rotation be uploaded and applied.
  38. * @param {boolean} [properties.uvs=false] - When true, uvs be uploaded and applied.
  39. * @param {boolean} [properties.tint=false] - When true, alpha and tint be uploaded and applied.
  40. * @param {number} [batchSize=16384] - Number of particles per batch. If less than maxSize, it uses maxSize instead.
  41. * @param {boolean} [autoResize=false] If true, container allocates more batches in case
  42. * there are more than `maxSize` particles.
  43. */
  44. constructor(maxSize = 1500, properties, batchSize = 16384, autoResize = false)
  45. {
  46. super();
  47. // Making sure the batch size is valid
  48. // 65535 is max vertex index in the index buffer (see ParticleRenderer)
  49. // so max number of particles is 65536 / 4 = 16384
  50. const maxBatchSize = 16384;
  51. if (batchSize > maxBatchSize)
  52. {
  53. batchSize = maxBatchSize;
  54. }
  55. if (batchSize > maxSize)
  56. {
  57. batchSize = maxSize;
  58. }
  59. /**
  60. * Set properties to be dynamic (true) / static (false)
  61. *
  62. * @member {boolean[]}
  63. * @private
  64. */
  65. this._properties = [false, true, false, false, false];
  66. /**
  67. * @member {number}
  68. * @private
  69. */
  70. this._maxSize = maxSize;
  71. /**
  72. * @member {number}
  73. * @private
  74. */
  75. this._batchSize = batchSize;
  76. /**
  77. * @member {object<number, WebGLBuffer>}
  78. * @private
  79. */
  80. this._glBuffers = {};
  81. /**
  82. * for every batch stores _updateID corresponding to the last change in that batch
  83. * @member {number[]}
  84. * @private
  85. */
  86. this._bufferUpdateIDs = [];
  87. /**
  88. * when child inserted, removed or changes position this number goes up
  89. * @member {number[]}
  90. * @private
  91. */
  92. this._updateID = 0;
  93. /**
  94. * @member {boolean}
  95. *
  96. */
  97. this.interactiveChildren = false;
  98. /**
  99. * The blend mode to be applied to the sprite. Apply a value of `PIXI.BLEND_MODES.NORMAL`
  100. * to reset the blend mode.
  101. *
  102. * @member {number}
  103. * @default PIXI.BLEND_MODES.NORMAL
  104. * @see PIXI.BLEND_MODES
  105. */
  106. this.blendMode = core.BLEND_MODES.NORMAL;
  107. /**
  108. * If true, container allocates more batches in case there are more than `maxSize` particles.
  109. * @member {boolean}
  110. * @default false
  111. */
  112. this.autoResize = autoResize;
  113. /**
  114. * Used for canvas renderering. If true then the elements will be positioned at the
  115. * nearest pixel. This provides a nice speed boost.
  116. *
  117. * @member {boolean}
  118. * @default true;
  119. */
  120. this.roundPixels = true;
  121. /**
  122. * The texture used to render the children.
  123. *
  124. * @readonly
  125. * @member {BaseTexture}
  126. */
  127. this.baseTexture = null;
  128. this.setProperties(properties);
  129. /**
  130. * The tint applied to the container.
  131. * This is a hex value. A value of 0xFFFFFF will remove any tint effect.
  132. *
  133. * @private
  134. * @member {number}
  135. * @default 0xFFFFFF
  136. */
  137. this._tint = 0;
  138. this.tintRgb = new Float32Array(4);
  139. this.tint = 0xFFFFFF;
  140. }
  141. /**
  142. * Sets the private properties array to dynamic / static based on the passed properties object
  143. *
  144. * @param {object} properties - The properties to be uploaded
  145. */
  146. setProperties(properties)
  147. {
  148. if (properties)
  149. {
  150. this._properties[0] = 'vertices' in properties || 'scale' in properties
  151. ? !!properties.vertices || !!properties.scale : this._properties[0];
  152. this._properties[1] = 'position' in properties ? !!properties.position : this._properties[1];
  153. this._properties[2] = 'rotation' in properties ? !!properties.rotation : this._properties[2];
  154. this._properties[3] = 'uvs' in properties ? !!properties.uvs : this._properties[3];
  155. this._properties[4] = 'tint' in properties || 'alpha' in properties
  156. ? !!properties.tint || !!properties.alpha : this._properties[4];
  157. }
  158. }
  159. /**
  160. * Updates the object transform for rendering
  161. *
  162. * @private
  163. */
  164. updateTransform()
  165. {
  166. // TODO don't need to!
  167. this.displayObjectUpdateTransform();
  168. // PIXI.Container.prototype.updateTransform.call( this );
  169. }
  170. /**
  171. * The tint applied to the container. This is a hex value.
  172. * A value of 0xFFFFFF will remove any tint effect.
  173. ** IMPORTANT: This is a webGL only feature and will be ignored by the canvas renderer.
  174. * @member {number}
  175. * @default 0xFFFFFF
  176. */
  177. get tint()
  178. {
  179. return this._tint;
  180. }
  181. set tint(value) // eslint-disable-line require-jsdoc
  182. {
  183. this._tint = value;
  184. hex2rgb(value, this.tintRgb);
  185. }
  186. /**
  187. * Renders the container using the WebGL renderer
  188. *
  189. * @private
  190. * @param {PIXI.WebGLRenderer} renderer - The webgl renderer
  191. */
  192. renderWebGL(renderer)
  193. {
  194. if (!this.visible || this.worldAlpha <= 0 || !this.children.length || !this.renderable)
  195. {
  196. return;
  197. }
  198. if (!this.baseTexture)
  199. {
  200. this.baseTexture = this.children[0]._texture.baseTexture;
  201. if (!this.baseTexture.hasLoaded)
  202. {
  203. this.baseTexture.once('update', () => this.onChildrenChange(0));
  204. }
  205. }
  206. renderer.setObjectRenderer(renderer.plugins.particle);
  207. renderer.plugins.particle.render(this);
  208. }
  209. /**
  210. * Set the flag that static data should be updated to true
  211. *
  212. * @private
  213. * @param {number} smallestChildIndex - The smallest child index
  214. */
  215. onChildrenChange(smallestChildIndex)
  216. {
  217. const bufferIndex = Math.floor(smallestChildIndex / this._batchSize);
  218. while (this._bufferUpdateIDs.length < bufferIndex)
  219. {
  220. this._bufferUpdateIDs.push(0);
  221. }
  222. this._bufferUpdateIDs[bufferIndex] = ++this._updateID;
  223. }
  224. /**
  225. * Renders the object using the Canvas renderer
  226. *
  227. * @private
  228. * @param {PIXI.CanvasRenderer} renderer - The canvas renderer
  229. */
  230. renderCanvas(renderer)
  231. {
  232. if (!this.visible || this.worldAlpha <= 0 || !this.children.length || !this.renderable)
  233. {
  234. return;
  235. }
  236. const context = renderer.context;
  237. const transform = this.worldTransform;
  238. let isRotated = true;
  239. let positionX = 0;
  240. let positionY = 0;
  241. let finalWidth = 0;
  242. let finalHeight = 0;
  243. renderer.setBlendMode(this.blendMode);
  244. context.globalAlpha = this.worldAlpha;
  245. this.displayObjectUpdateTransform();
  246. for (let i = 0; i < this.children.length; ++i)
  247. {
  248. const child = this.children[i];
  249. if (!child.visible)
  250. {
  251. continue;
  252. }
  253. const frame = child._texture.frame;
  254. context.globalAlpha = this.worldAlpha * child.alpha;
  255. if (child.rotation % (Math.PI * 2) === 0)
  256. {
  257. // this is the fastest way to optimise! - if rotation is 0 then we can avoid any kind of setTransform call
  258. if (isRotated)
  259. {
  260. context.setTransform(
  261. transform.a,
  262. transform.b,
  263. transform.c,
  264. transform.d,
  265. transform.tx * renderer.resolution,
  266. transform.ty * renderer.resolution
  267. );
  268. isRotated = false;
  269. }
  270. positionX = ((child.anchor.x) * (-frame.width * child.scale.x)) + child.position.x + 0.5;
  271. positionY = ((child.anchor.y) * (-frame.height * child.scale.y)) + child.position.y + 0.5;
  272. finalWidth = frame.width * child.scale.x;
  273. finalHeight = frame.height * child.scale.y;
  274. }
  275. else
  276. {
  277. if (!isRotated)
  278. {
  279. isRotated = true;
  280. }
  281. child.displayObjectUpdateTransform();
  282. const childTransform = child.worldTransform;
  283. if (renderer.roundPixels)
  284. {
  285. context.setTransform(
  286. childTransform.a,
  287. childTransform.b,
  288. childTransform.c,
  289. childTransform.d,
  290. (childTransform.tx * renderer.resolution) | 0,
  291. (childTransform.ty * renderer.resolution) | 0
  292. );
  293. }
  294. else
  295. {
  296. context.setTransform(
  297. childTransform.a,
  298. childTransform.b,
  299. childTransform.c,
  300. childTransform.d,
  301. childTransform.tx * renderer.resolution,
  302. childTransform.ty * renderer.resolution
  303. );
  304. }
  305. positionX = ((child.anchor.x) * (-frame.width)) + 0.5;
  306. positionY = ((child.anchor.y) * (-frame.height)) + 0.5;
  307. finalWidth = frame.width;
  308. finalHeight = frame.height;
  309. }
  310. const resolution = child._texture.baseTexture.resolution;
  311. context.drawImage(
  312. child._texture.baseTexture.source,
  313. frame.x * resolution,
  314. frame.y * resolution,
  315. frame.width * resolution,
  316. frame.height * resolution,
  317. positionX * renderer.resolution,
  318. positionY * renderer.resolution,
  319. finalWidth * renderer.resolution,
  320. finalHeight * renderer.resolution
  321. );
  322. }
  323. }
  324. /**
  325. * Destroys the container
  326. *
  327. * @param {object|boolean} [options] - Options parameter. A boolean will act as if all options
  328. * have been set to that value
  329. * @param {boolean} [options.children=false] - if set to true, all the children will have their
  330. * destroy method called as well. 'options' will be passed on to those calls.
  331. * @param {boolean} [options.texture=false] - Only used for child Sprites if options.children is set to true
  332. * Should it destroy the texture of the child sprite
  333. * @param {boolean} [options.baseTexture=false] - Only used for child Sprites if options.children is set to true
  334. * Should it destroy the base texture of the child sprite
  335. */
  336. destroy(options)
  337. {
  338. super.destroy(options);
  339. if (this._buffers)
  340. {
  341. for (let i = 0; i < this._buffers.length; ++i)
  342. {
  343. this._buffers[i].destroy();
  344. }
  345. }
  346. this._properties = null;
  347. this._buffers = null;
  348. this._bufferUpdateIDs = null;
  349. }
  350. }