- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 57
- 58
- 59
- 60
- 61
- 62
- 63
- 64
- 65
- 66
- 67
- 68
- 69
- 70
- 71
- 72
- 73
- 74
- 75
- 76
- 77
- 78
- 79
- 80
- 81
- 82
- 83
- 84
- 85
- 86
- 87
- 88
- 89
- 90
- 91
- 92
- 93
- 94
- 95
- 96
- 97
- 98
- 99
- 100
- 101
- 102
- 103
- 104
- 105
- 106
- 107
- 108
- 109
- 110
- 111
- 112
- 113
- 114
- 115
- 116
- 117
- 118
- 119
- 120
- 121
- 122
- 123
- 124
- 125
- 126
- 127
- 128
- 129
- 130
- 131
- 132
- 133
- 134
- 135
- 136
- 137
- 138
- 139
- 140
- 141
- 142
- 143
- 144
- 145
- 146
- 147
- 148
- 149
- 150
- 151
- 152
- 153
- 154
- 155
- 156
- 157
- 158
- 159
- 160
- 161
- 162
- 163
- 164
- 165
- 166
- 167
- 168
- 169
- 170
- 171
- 172
- 173
- 174
- 175
- 176
- 177
- 178
- 179
- 180
- 181
- 182
- 183
- 184
- 185
- 186
- 187
- 188
- 189
- 190
- 191
- 192
- 193
- 194
- 195
- 196
- 197
- 198
- 199
- 200
- 201
- 202
- 203
- 204
- 205
- 206
- 207
- 208
- 209
- 210
- 211
- 212
- 213
- 214
- 215
- 216
- 217
- 218
- 219
- 220
- 221
- 222
- 223
- 224
- 225
- 226
- 227
- 228
- 229
- 230
- 231
- 232
- 233
- 234
- 235
- 236
- 237
- 238
- 239
- 240
- 241
- 242
- 243
- 244
- 245
- 246
- 247
- 248
- 249
- 250
- 251
- 252
- 253
- 254
- 255
- 256
- 257
- 258
- 259
- 260
- 261
- 262
- 263
- 264
- 265
- 266
- 267
- 268
- 269
- 270
- 271
- 272
- 273
import { BLEND_MODES } from '@pixi/constants';
import { Container } from '@pixi/display';
import { hex2rgb } from '@pixi/utils';
import type { BaseTexture, Renderer } from '@pixi/core';
import type { ParticleBuffer } from './ParticleBuffer';
import type { IDestroyOptions } from '@pixi/display';
import type { Sprite } from '@pixi/sprite';
export interface IParticleProperties
{
vertices?: boolean;
position?: boolean;
rotation?: boolean;
uvs?: boolean;
tint?: boolean;
alpha?: boolean;
scale?: boolean;
}
/**
* The ParticleContainer class is a really fast version of the Container built solely for speed,
* so use when you need a lot of sprites or particles.
*
* The tradeoff of the ParticleContainer is that most advanced functionality will not work.
* ParticleContainer implements the basic object transform (position, scale, rotation)
* and some advanced functionality like tint (as of v4.5.6).
*
* Other more advanced functionality like masking, children, filters, etc will not work on sprites in this batch.
*
* It's extremely easy to use:
* ```js
* let container = new ParticleContainer();
*
* for (let i = 0; i < 100; ++i)
* {
* let sprite = PIXI.Sprite.from("myImage.png");
* container.addChild(sprite);
* }
* ```
*
* And here you have a hundred sprites that will be rendered at the speed of light.
* @memberof PIXI
*/
export class ParticleContainer extends Container<Sprite>
{
/**
* The blend mode to be applied to the sprite. Apply a value of `PIXI.BLEND_MODES.NORMAL`
* to reset the blend mode.
* @default PIXI.BLEND_MODES.NORMAL
*/
public blendMode: BLEND_MODES;
/**
* If true, container allocates more batches in case there are more than `maxSize` particles.
* @default false
*/
public autoResize: boolean;
/**
* If true PixiJS will Math.floor() x/y values when rendering, stopping pixel interpolation.
* Advantages can include sharper image quality (like text) and faster rendering on canvas.
* The main disadvantage is movement of objects may appear less smooth.
* Default to true here as performance is usually the priority for particles.
* @default true
*/
public roundPixels: boolean;
/**
* The texture used to render the children.
* @readonly
*/
public baseTexture: BaseTexture;
public tintRgb: Float32Array;
/** @private */
_maxSize: number;
/** @private */
_buffers: ParticleBuffer[];
/** @private */
_batchSize: number;
/**
* Set properties to be dynamic (true) / static (false).
* @private
*/
_properties: boolean[];
/**
* For every batch, stores _updateID corresponding to the last change in that batch.
* @private
*/
_bufferUpdateIDs: number[];
/**
* When child inserted, removed or changes position this number goes up.
* @private
*/
_updateID: number;
/**
* The tint applied to the container.
* This is a hex value. A value of 0xFFFFFF will remove any tint effect.
* @default 0xFFFFFF
*/
private _tint: number;
/**
* @param maxSize - The maximum number of particles that can be rendered by the container.
* Affects size of allocated buffers.
* @param properties - The properties of children that should be uploaded to the gpu and applied.
* @param {boolean} [properties.vertices=false] - When true, vertices be uploaded and applied.
* if sprite's ` scale/anchor/trim/frame/orig` is dynamic, please set `true`.
* @param {boolean} [properties.position=true] - When true, position be uploaded and applied.
* @param {boolean} [properties.rotation=false] - When true, rotation be uploaded and applied.
* @param {boolean} [properties.uvs=false] - When true, uvs be uploaded and applied.
* @param {boolean} [properties.tint=false] - When true, alpha and tint be uploaded and applied.
* @param {number} [batchSize=16384] - Number of particles per batch. If less than maxSize, it uses maxSize instead.
* @param {boolean} [autoResize=false] - If true, container allocates more batches in case
* there are more than `maxSize` particles.
*/
constructor(maxSize = 1500, properties?: IParticleProperties, batchSize = 16384, autoResize = false)
{
super();
// Making sure the batch size is valid
// 65535 is max vertex index in the index buffer (see ParticleRenderer)
// so max number of particles is 65536 / 4 = 16384
const maxBatchSize = 16384;
if (batchSize > maxBatchSize)
{
batchSize = maxBatchSize;
}
this._properties = [false, true, false, false, false];
this._maxSize = maxSize;
this._batchSize = batchSize;
this._buffers = null;
this._bufferUpdateIDs = [];
this._updateID = 0;
this.interactiveChildren = false;
this.blendMode = BLEND_MODES.NORMAL;
this.autoResize = autoResize;
this.roundPixels = true;
this.baseTexture = null;
this.setProperties(properties);
this._tint = 0;
this.tintRgb = new Float32Array(4);
this.tint = 0xFFFFFF;
}
/**
* Sets the private properties array to dynamic / static based on the passed properties object
* @param properties - The properties to be uploaded
*/
public setProperties(properties: IParticleProperties): void
{
if (properties)
{
this._properties[0] = 'vertices' in properties || 'scale' in properties
? !!properties.vertices || !!properties.scale : this._properties[0];
this._properties[1] = 'position' in properties ? !!properties.position : this._properties[1];
this._properties[2] = 'rotation' in properties ? !!properties.rotation : this._properties[2];
this._properties[3] = 'uvs' in properties ? !!properties.uvs : this._properties[3];
this._properties[4] = 'tint' in properties || 'alpha' in properties
? !!properties.tint || !!properties.alpha : this._properties[4];
}
}
updateTransform(): void
{
// TODO don't need to!
this.displayObjectUpdateTransform();
}
/**
* The tint applied to the container. This is a hex value.
* A value of 0xFFFFFF will remove any tint effect.
* IMPORTANT: This is a WebGL only feature and will be ignored by the canvas renderer.
* @default 0xFFFFFF
*/
get tint(): number
{
return this._tint;
}
set tint(value: number)
{
this._tint = value;
hex2rgb(value, this.tintRgb);
}
/**
* Renders the container using the WebGL renderer.
* @param renderer - The WebGL renderer.
*/
public render(renderer: Renderer): void
{
if (!this.visible || this.worldAlpha <= 0 || !this.children.length || !this.renderable)
{
return;
}
if (!this.baseTexture)
{
this.baseTexture = this.children[0]._texture.baseTexture;
if (!this.baseTexture.valid)
{
this.baseTexture.once('update', () => this.onChildrenChange(0));
}
}
renderer.batch.setObjectRenderer(renderer.plugins.particle);
renderer.plugins.particle.render(this);
}
/**
* Set the flag that static data should be updated to true
* @param smallestChildIndex - The smallest child index.
*/
protected onChildrenChange(smallestChildIndex: number): void
{
const bufferIndex = Math.floor(smallestChildIndex / this._batchSize);
while (this._bufferUpdateIDs.length < bufferIndex)
{
this._bufferUpdateIDs.push(0);
}
this._bufferUpdateIDs[bufferIndex] = ++this._updateID;
}
public dispose(): void
{
if (this._buffers)
{
for (let i = 0; i < this._buffers.length; ++i)
{
this._buffers[i].destroy();
}
this._buffers = null;
}
}
/**
* Destroys the container
* @param options - Options parameter. A boolean will act as if all options
* have been set to that value
* @param {boolean} [options.children=false] - if set to true, all the children will have their
* destroy method called as well. 'options' will be passed on to those calls.
* @param {boolean} [options.texture=false] - Only used for child Sprites if options.children is set to true
* Should it destroy the texture of the child sprite
* @param {boolean} [options.baseTexture=false] - Only used for child Sprites if options.children is set to true
* Should it destroy the base texture of the child sprite
*/
public destroy(options?: IDestroyOptions | boolean): void
{
super.destroy(options);
this.dispose();
this._properties = null;
this._buffers = null;
this._bufferUpdateIDs = null;
}
}