babylon.quarks 0.17.4 → 0.17.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (78) hide show
  1. package/CHANGELOG.md +103 -0
  2. package/LICENSE +21 -21
  3. package/README.md +169 -33
  4. package/dist/babylon.quarks.cjs +1 -1
  5. package/dist/babylon.quarks.cjs.map +1 -1
  6. package/dist/babylon.quarks.esm.js +1 -1
  7. package/dist/babylon.quarks.esm.js.map +1 -1
  8. package/dist/babylon.quarks.umd.min.js +2 -0
  9. package/dist/babylon.quarks.umd.min.js.map +1 -0
  10. package/dist/types/index.d.ts +2829 -19
  11. package/package.json +62 -60
  12. package/src/BatchedParticleRenderer.ts +3 -3
  13. package/src/BatchedRenderer.ts +228 -227
  14. package/src/MeshSurfaceEmitter.ts +137 -137
  15. package/src/ParticleEmitter.ts +86 -86
  16. package/src/ParticleSystem.ts +1322 -1285
  17. package/src/QuarksLoader.ts +529 -510
  18. package/src/QuarksPrefab.ts +307 -307
  19. package/src/QuarksUtil.ts +66 -66
  20. package/src/SpriteBatch.ts +403 -404
  21. package/src/TrailBatch.ts +420 -419
  22. package/src/VFXBatch.ts +108 -108
  23. package/src/geometryUtil.ts +15 -15
  24. package/src/index.ts +59 -52
  25. package/src/materials/ParticleMaterials.ts +23 -23
  26. package/src/materials/index.ts +1 -1
  27. package/src/shaders/chunks/index.ts +1 -1
  28. package/src/shaders/chunks/register-shader-chunks.ts +32 -32
  29. package/src/shaders/chunks/soft_fragment.glsl.ts +13 -13
  30. package/src/shaders/chunks/soft_pars_fragment.glsl.ts +20 -20
  31. package/src/shaders/chunks/soft_pars_vertex.glsl.ts +6 -6
  32. package/src/shaders/chunks/soft_vertex.glsl.ts +6 -6
  33. package/src/shaders/chunks/tile_fragment.glsl.ts +9 -9
  34. package/src/shaders/chunks/tile_pars_fragment.glsl.ts +9 -9
  35. package/src/shaders/chunks/tile_pars_vertex.glsl.ts +28 -28
  36. package/src/shaders/chunks/tile_vertex.glsl.ts +18 -18
  37. package/src/shaders/index.ts +9 -9
  38. package/src/shaders/local_particle_physics_vert.glsl.ts +79 -79
  39. package/src/shaders/local_particle_vert.glsl.ts +50 -50
  40. package/src/shaders/particle_frag.glsl.ts +61 -61
  41. package/src/shaders/particle_physics_frag.glsl.ts +67 -67
  42. package/src/shaders/particle_vert.glsl.ts +85 -85
  43. package/src/shaders/stretched_bb_particle_vert.glsl.ts +76 -74
  44. package/src/shaders/trail_frag.glsl.ts +21 -21
  45. package/src/shaders/trail_vert.glsl.ts +58 -58
  46. package/dist/types/BatchedParticleRenderer.d.ts +0 -2
  47. package/dist/types/BatchedRenderer.d.ts +0 -59
  48. package/dist/types/MeshSurfaceEmitter.d.ts +0 -19
  49. package/dist/types/ParticleEmitter.d.ts +0 -16
  50. package/dist/types/ParticleSystem.d.ts +0 -234
  51. package/dist/types/QuarksLoader.d.ts +0 -28
  52. package/dist/types/QuarksPrefab.d.ts +0 -48
  53. package/dist/types/QuarksUtil.d.ts +0 -14
  54. package/dist/types/SpriteBatch.d.ts +0 -33
  55. package/dist/types/TrailBatch.d.ts +0 -28
  56. package/dist/types/VFXBatch.d.ts +0 -51
  57. package/dist/types/geometryUtil.d.ts +0 -2
  58. package/dist/types/materials/ParticleMaterials.d.ts +0 -12
  59. package/dist/types/materials/index.d.ts +0 -1
  60. package/dist/types/shaders/chunks/index.d.ts +0 -1
  61. package/dist/types/shaders/chunks/register-shader-chunks.d.ts +0 -3
  62. package/dist/types/shaders/chunks/soft_fragment.glsl.d.ts +0 -2
  63. package/dist/types/shaders/chunks/soft_pars_fragment.glsl.d.ts +0 -2
  64. package/dist/types/shaders/chunks/soft_pars_vertex.glsl.d.ts +0 -2
  65. package/dist/types/shaders/chunks/soft_vertex.glsl.d.ts +0 -2
  66. package/dist/types/shaders/chunks/tile_fragment.glsl.d.ts +0 -2
  67. package/dist/types/shaders/chunks/tile_pars_fragment.glsl.d.ts +0 -2
  68. package/dist/types/shaders/chunks/tile_pars_vertex.glsl.d.ts +0 -2
  69. package/dist/types/shaders/chunks/tile_vertex.glsl.d.ts +0 -2
  70. package/dist/types/shaders/index.d.ts +0 -9
  71. package/dist/types/shaders/local_particle_physics_vert.glsl.d.ts +0 -2
  72. package/dist/types/shaders/local_particle_vert.glsl.d.ts +0 -2
  73. package/dist/types/shaders/particle_frag.glsl.d.ts +0 -2
  74. package/dist/types/shaders/particle_physics_frag.glsl.d.ts +0 -2
  75. package/dist/types/shaders/particle_vert.glsl.d.ts +0 -2
  76. package/dist/types/shaders/stretched_bb_particle_vert.glsl.d.ts +0 -2
  77. package/dist/types/shaders/trail_frag.glsl.d.ts +0 -2
  78. package/dist/types/shaders/trail_vert.glsl.d.ts +0 -2
@@ -1,1285 +1,1322 @@
1
- import {Scene} from '@babylonjs/core/scene';
2
- import {Texture} from '@babylonjs/core/Materials/Textures/texture';
3
- import {Constants} from '@babylonjs/core/Engines/constants';
4
- import {
5
- AxisAngleGenerator,
6
- ColorGenerator,
7
- ColorGeneratorFromJSON,
8
- ConstantColor,
9
- ConstantValue,
10
- FunctionColorGenerator,
11
- FunctionJSON,
12
- FunctionValueGenerator,
13
- GeneratorFromJSON,
14
- ValueGenerator,
15
- ValueGeneratorFromJSON,
16
- Behavior,
17
- BehaviorFromJSON,
18
- Particle,
19
- SpriteParticle,
20
- TrailParticle,
21
- EmitterFromJSON,
22
- EmitterShape,
23
- SphereEmitter,
24
- RendererEmitterSettings,
25
- RotationGenerator,
26
- IParticleSystem,
27
- EmissionState,
28
- GeneratorMemory,
29
- TrailSettings,
30
- StretchedBillBoardSettings,
31
- SerializationOptions,
32
- Vector3,
33
- Vector4,
34
- Matrix3,
35
- Matrix4,
36
- Quaternion,
37
- Vector3Generator,
38
- ParticleSystemEvent,
39
- ParticleSystemEventType,
40
- } from 'quarks.core';
41
- import {ParticleEmitter} from './ParticleEmitter';
42
- import {RenderMode} from './VFXBatch';
43
- import {BatchedRenderer, VFXBatchSettings} from './BatchedRenderer';
44
- import {ensureTriangleIndices} from './geometryUtil';
45
-
46
- export interface BurstParameters {
47
- time: number;
48
- count: ValueGenerator | FunctionValueGenerator;
49
- cycle: number;
50
- interval: number;
51
- probability: number;
52
- }
53
-
54
- const UP = new Vector3(0, 0, 1);
55
- const tempQ = new Quaternion();
56
- const tempV = new Vector3();
57
- const tempV2 = new Vector3();
58
- const PREWARM_FPS = 60;
59
-
60
- const DEFAULT_POSITIONS = new Float32Array([
61
- -0.5, -0.5, 0,
62
- 0.5, -0.5, 0,
63
- 0.5, 0.5, 0,
64
- -0.5, 0.5, 0,
65
- ]);
66
- const DEFAULT_UVS = new Float32Array([0, 0, 1, 0, 1, 1, 0, 1]);
67
- const DEFAULT_INDICES = new Uint32Array([0, 1, 2, 0, 2, 3]);
68
-
69
- export interface ParticleSystemParameters {
70
- autoDestroy?: boolean;
71
- looping?: boolean;
72
- prewarm?: boolean;
73
- duration?: number;
74
- shape?: EmitterShape;
75
- startLife?: ValueGenerator | FunctionValueGenerator;
76
- startSpeed?: ValueGenerator | FunctionValueGenerator;
77
- startRotation?: ValueGenerator | FunctionValueGenerator | RotationGenerator;
78
- startSize?: ValueGenerator | FunctionValueGenerator | Vector3Generator;
79
- startLength?: ValueGenerator | FunctionValueGenerator;
80
- startColor?: ColorGenerator | FunctionColorGenerator;
81
- emissionOverTime?: ValueGenerator | FunctionValueGenerator;
82
- emissionOverDistance?: ValueGenerator | FunctionValueGenerator;
83
- emissionBursts?: Array<BurstParameters>;
84
- onlyUsedByOther?: boolean;
85
- behaviors?: Array<Behavior>;
86
- instancingGeometry?: Float32Array;
87
- instancingIndices?: Uint32Array | Uint16Array;
88
- instancingUVs?: Float32Array;
89
- instancingNormals?: Float32Array;
90
- renderMode?: RenderMode;
91
- rendererEmitterSettings?: RendererEmitterSettings;
92
- speedFactor?: number;
93
- material?: any;
94
- texture?: Texture | null;
95
- startTileIndex?: ValueGenerator;
96
- uTileCount?: number;
97
- vTileCount?: number;
98
- blendTiles?: boolean;
99
- softParticles?: boolean;
100
- softFarFade?: number;
101
- softNearFade?: number;
102
- renderOrder?: number;
103
- worldSpace?: boolean;
104
- blendMode?: number;
105
- transparent?: boolean;
106
- depthTest?: boolean;
107
- depthWrite?: boolean;
108
- alphaTest?: number;
109
- layerMask?: number;
110
- scene?: Scene;
111
- }
112
-
113
- export interface BurstParametersJSON {
114
- time: number;
115
- count: FunctionJSON | number;
116
- cycle: number;
117
- interval: number;
118
- probability: number;
119
- }
120
-
121
- export interface ParticleSystemJSONParameters {
122
- version: string;
123
- autoDestroy: boolean;
124
- looping: boolean;
125
- prewarm: boolean;
126
- duration: number;
127
- shape: any;
128
- startLife: FunctionJSON;
129
- startSpeed: FunctionJSON;
130
- startRotation: FunctionJSON;
131
- startSize: FunctionJSON;
132
- startColor: FunctionJSON;
133
- emissionOverTime: FunctionJSON;
134
- emissionOverDistance: FunctionJSON;
135
- emissionBursts?: Array<BurstParametersJSON>;
136
- onlyUsedByOther: boolean;
137
- rendererEmitterSettings: RendererEmitterSettings;
138
- instancingGeometry?: any;
139
- renderMode: number;
140
- renderOrder?: number;
141
- speedFactor?: number;
142
- texture?: string;
143
- material?: string;
144
- layers?: number;
145
- startTileIndex: FunctionJSON | number;
146
- uTileCount: number;
147
- vTileCount: number;
148
- blendTiles?: boolean;
149
- softParticles?: boolean;
150
- softFarFade?: number;
151
- softNearFade?: number;
152
- blending?: number;
153
- transparent?: boolean;
154
- depthTest?: boolean;
155
- depthWrite?: boolean;
156
- alphaTest?: number;
157
- behaviors: Array<any>;
158
- worldSpace: boolean;
159
- }
160
-
161
- export interface BabylonMetaData {
162
- textures: {[uuid: string]: Texture | null};
163
- materials: {[uuid: string]: any};
164
- geometries: {[uuid: string]: any};
165
- images?: {[uuid: string]: any};
166
- shapes?: {[uuid: string]: any};
167
- skeletons?: {[uuid: string]: any};
168
- animations?: {[uuid: string]: any};
169
- nodes?: {[uuid: string]: any};
170
- }
171
-
172
- export class ParticleSystem implements IParticleSystem {
173
- private static serializationCounter = 0;
174
-
175
- autoDestroy: boolean;
176
- prewarm: boolean;
177
- looping: boolean;
178
- duration: number;
179
- startLife: ValueGenerator | FunctionValueGenerator;
180
- startSpeed: ValueGenerator | FunctionValueGenerator;
181
- startRotation: ValueGenerator | FunctionValueGenerator | RotationGenerator;
182
- startSize: ValueGenerator | FunctionValueGenerator | Vector3Generator;
183
- startColor: ColorGenerator | FunctionColorGenerator;
184
- startTileIndex: ValueGenerator;
185
- rendererEmitterSettings: RendererEmitterSettings;
186
- emissionOverTime: ValueGenerator | FunctionValueGenerator;
187
- emissionOverDistance: ValueGenerator | FunctionValueGenerator;
188
- emissionBursts: Array<BurstParameters>;
189
- onlyUsedByOther: boolean;
190
- worldSpace: boolean;
191
- particleNum: number;
192
- paused: boolean;
193
- particles: Array<Particle>;
194
- emitterShape: EmitterShape;
195
- emitter: ParticleEmitter;
196
- rendererSettings: VFXBatchSettings;
197
- neededToUpdateRender: boolean;
198
- behaviors: Array<Behavior>;
199
- emissionState: EmissionState;
200
-
201
- private prewarmed: boolean;
202
- private emitEnded: boolean;
203
- private markForDestroy: boolean;
204
- private temp: Vector3 = new Vector3();
205
- private normalMatrix: Matrix3 = new Matrix3();
206
- private memory: GeneratorMemory = [];
207
- private listeners: {[event: string]: Array<(event: ParticleSystemEvent) => void>} = {};
208
- private readonly layerMaskProxy: {mask: number};
209
- private materialRef: any = null;
210
- private qualityFactor = 1;
211
- private useFastTrailHistory = true;
212
- /** @internal **/
213
- _renderer?: BatchedRenderer;
214
-
215
- set time(time: number) {
216
- this.emissionState.time = time;
217
- }
218
-
219
- get time(): number {
220
- return this.emissionState.time;
221
- }
222
-
223
- get layers() {
224
- return this.layerMaskProxy;
225
- }
226
-
227
- get texture() {
228
- return this.rendererSettings.texture;
229
- }
230
-
231
- set texture(texture: Texture | null) {
232
- this.rendererSettings.texture = texture;
233
- this.neededToUpdateRender = true;
234
- }
235
-
236
- get material() {
237
- return this.materialRef;
238
- }
239
-
240
- set material(material: any) {
241
- this.materialRef = material;
242
- this.applyMaterialSettings(material);
243
- }
244
-
245
- get uTileCount() {
246
- return this.rendererSettings.uTileCount;
247
- }
248
-
249
- set uTileCount(u: number) {
250
- this.rendererSettings.uTileCount = u;
251
- this.neededToUpdateRender = true;
252
- }
253
-
254
- get vTileCount() {
255
- return this.rendererSettings.vTileCount;
256
- }
257
-
258
- set vTileCount(v: number) {
259
- this.rendererSettings.vTileCount = v;
260
- this.neededToUpdateRender = true;
261
- }
262
-
263
- get blendTiles() {
264
- return this.rendererSettings.blendTiles;
265
- }
266
-
267
- set blendTiles(v: boolean) {
268
- this.rendererSettings.blendTiles = v;
269
- this.neededToUpdateRender = true;
270
- }
271
-
272
- get softParticles() {
273
- return this.rendererSettings.softParticles;
274
- }
275
-
276
- set softParticles(v: boolean) {
277
- this.rendererSettings.softParticles = v;
278
- this.neededToUpdateRender = true;
279
- }
280
-
281
- get softNearFade() {
282
- return this.rendererSettings.softNearFade;
283
- }
284
-
285
- set softNearFade(v: number) {
286
- this.rendererSettings.softNearFade = v;
287
- this.neededToUpdateRender = true;
288
- }
289
-
290
- get softFarFade() {
291
- return this.rendererSettings.softFarFade;
292
- }
293
-
294
- set softFarFade(v: number) {
295
- this.rendererSettings.softFarFade = v;
296
- this.neededToUpdateRender = true;
297
- }
298
-
299
- get instancingGeometry(): Float32Array {
300
- return this.rendererSettings.instancingGeometry;
301
- }
302
-
303
- set instancingGeometry(geometry: Float32Array) {
304
- this.restart();
305
- this.particles.length = 0;
306
- this.rendererSettings.instancingGeometry = geometry;
307
- this.neededToUpdateRender = true;
308
- }
309
-
310
- get blending() {
311
- return this.rendererSettings.materialBlendMode;
312
- }
313
-
314
- set blending(blending: number) {
315
- this.rendererSettings.materialBlendMode = blending;
316
- this.neededToUpdateRender = true;
317
- }
318
-
319
- get renderMode(): RenderMode {
320
- return this.rendererSettings.renderMode;
321
- }
322
-
323
- set renderMode(renderMode: RenderMode) {
324
- if (this.rendererSettings.renderMode !== renderMode) {
325
- let needRestart = false;
326
- if (this.rendererSettings.renderMode === RenderMode.Trail) {
327
- needRestart = true;
328
- }
329
- if (this.rendererSettings.renderMode === RenderMode.Mesh) {
330
- this.startRotation = new ConstantValue(0);
331
- }
332
- switch (renderMode) {
333
- case RenderMode.Trail:
334
- this.rendererEmitterSettings = {startLength: new ConstantValue(30), followLocalOrigin: false};
335
- needRestart = true;
336
- break;
337
- case RenderMode.Mesh:
338
- this.rendererEmitterSettings = {};
339
- this.startRotation = new AxisAngleGenerator(new Vector3(0, 1, 0), new ConstantValue(0));
340
- break;
341
- case RenderMode.StretchedBillBoard:
342
- this.rendererEmitterSettings = {speedFactor: 0, lengthFactor: 2};
343
- break;
344
- case RenderMode.BillBoard:
345
- case RenderMode.VerticalBillBoard:
346
- case RenderMode.HorizontalBillBoard:
347
- this.rendererEmitterSettings = {};
348
- break;
349
- }
350
- this.rendererSettings.renderMode = renderMode;
351
- if (needRestart) {
352
- this.restart();
353
- this.particles.length = 0;
354
- }
355
- this.neededToUpdateRender = true;
356
- }
357
- }
358
-
359
- get renderOrder(): number {
360
- return this.rendererSettings.renderOrder;
361
- }
362
-
363
- set renderOrder(renderOrder: number) {
364
- this.rendererSettings.renderOrder = renderOrder;
365
- this.neededToUpdateRender = true;
366
- }
367
-
368
- constructor(parameters: ParticleSystemParameters) {
369
- this.layerMaskProxy = {mask: parameters.layerMask ?? 0x0FFFFFFF};
370
- Object.defineProperty(this.layerMaskProxy, 'mask', {
371
- enumerable: true,
372
- get: () => this.rendererSettings.layerMask,
373
- set: (mask: number) => {
374
- this.rendererSettings.layerMask = mask;
375
- this.neededToUpdateRender = true;
376
- },
377
- });
378
-
379
- this.autoDestroy = parameters.autoDestroy ?? false;
380
- this.duration = parameters.duration ?? 1;
381
- this.looping = parameters.looping ?? true;
382
- this.prewarm = parameters.prewarm ?? false;
383
- this.startLife = parameters.startLife ?? new ConstantValue(5);
384
- this.startSpeed = parameters.startSpeed ?? new ConstantValue(0);
385
- this.startRotation = parameters.startRotation ?? new ConstantValue(0);
386
- this.startSize = parameters.startSize ?? new ConstantValue(1);
387
- this.startColor = parameters.startColor ?? new ConstantColor(new Vector4(1, 1, 1, 1));
388
- this.emissionOverTime = parameters.emissionOverTime ?? new ConstantValue(10);
389
- this.emissionOverDistance = parameters.emissionOverDistance ?? new ConstantValue(0);
390
- this.emissionBursts = parameters.emissionBursts ?? [];
391
- this.onlyUsedByOther = parameters.onlyUsedByOther ?? false;
392
- this.emitterShape = parameters.shape ?? new SphereEmitter();
393
- this.behaviors = parameters.behaviors ?? [];
394
- this.worldSpace = parameters.worldSpace ?? false;
395
- this.rendererEmitterSettings = parameters.rendererEmitterSettings ?? {};
396
-
397
- if (parameters.renderMode === RenderMode.StretchedBillBoard) {
398
- const settings = this.rendererEmitterSettings as StretchedBillBoardSettings;
399
- if (parameters.speedFactor !== undefined) {
400
- settings.speedFactor = parameters.speedFactor;
401
- }
402
- settings.speedFactor = settings.speedFactor ?? 0;
403
- settings.lengthFactor = settings.lengthFactor ?? 0;
404
- }
405
-
406
- this.rendererSettings = {
407
- instancingGeometry: parameters.instancingGeometry ?? DEFAULT_POSITIONS,
408
- instancingIndices: parameters.instancingIndices ?? DEFAULT_INDICES,
409
- instancingUVs: parameters.instancingUVs ?? DEFAULT_UVS,
410
- instancingNormals: parameters.instancingNormals,
411
- renderMode: parameters.renderMode ?? RenderMode.BillBoard,
412
- renderOrder: parameters.renderOrder ?? 0,
413
- uTileCount: parameters.uTileCount ?? 1,
414
- vTileCount: parameters.vTileCount ?? 1,
415
- blendTiles: parameters.blendTiles ?? false,
416
- softParticles: parameters.softParticles ?? false,
417
- softNearFade: parameters.softNearFade ?? 0,
418
- softFarFade: parameters.softFarFade ?? 0,
419
- materialBlendMode: parameters.blendMode ?? Constants.ALPHA_ADD,
420
- materialTransparent: parameters.transparent ?? true,
421
- materialDepthTest: parameters.depthTest ?? true,
422
- materialDepthWrite: parameters.depthWrite ?? false,
423
- materialAlphaTest: parameters.alphaTest ?? 0,
424
- texture: parameters.texture ?? null,
425
- layerMask: parameters.layerMask ?? 0x0FFFFFFF,
426
- };
427
- if (this.rendererSettings.renderMode === RenderMode.Mesh && !this.rendererSettings.instancingNormals) {
428
- this.rendererSettings.instancingNormals = ParticleSystem.createFallbackNormals(this.rendererSettings.instancingGeometry);
429
- }
430
-
431
- this.materialRef = parameters.material ?? null;
432
- this.applyMaterialSettings(this.materialRef, {
433
- blendMode: parameters.blendMode,
434
- transparent: parameters.transparent,
435
- depthTest: parameters.depthTest,
436
- depthWrite: parameters.depthWrite,
437
- alphaTest: parameters.alphaTest,
438
- texture: parameters.texture,
439
- layerMask: parameters.layerMask,
440
- });
441
- this.neededToUpdateRender = true;
442
-
443
- this.particles = [];
444
- this.startTileIndex = parameters.startTileIndex || new ConstantValue(0);
445
- this.emitter = new ParticleEmitter(this, parameters.scene);
446
-
447
- this.paused = false;
448
- this.particleNum = 0;
449
- this.emissionState = {
450
- isBursting: false,
451
- burstParticleIndex: 0,
452
- burstParticleCount: 0,
453
- burstIndex: 0,
454
- burstWaveIndex: 0,
455
- time: 0,
456
- waitEmiting: 0,
457
- travelDistance: 0,
458
- };
459
-
460
- this.emissionBursts.forEach((burst) => burst.count.startGen(this.memory));
461
- this.emissionOverDistance.startGen(this.memory);
462
- this.refreshTrailHistoryMode();
463
-
464
- this.emitEnded = false;
465
- this.markForDestroy = false;
466
- this.prewarmed = false;
467
- }
468
-
469
- private applyMaterialSettings(
470
- material: any,
471
- overrides: {
472
- blendMode?: number;
473
- transparent?: boolean;
474
- depthTest?: boolean;
475
- depthWrite?: boolean;
476
- alphaTest?: number;
477
- texture?: Texture | null;
478
- layerMask?: number;
479
- } = {}
480
- ) {
481
- /* istanbul ignore if -- rendererSettings is always assigned in the constructor before any code path can call this private method */
482
- if (!this.rendererSettings) {
483
- return;
484
- }
485
-
486
- const resolvedTexture =
487
- overrides.texture !== undefined
488
- ? overrides.texture
489
- : (material?.albedoTexture ??
490
- material?.diffuseTexture ??
491
- material?.emissiveTexture ??
492
- material?.opacityTexture ??
493
- material?.baseTexture ??
494
- this.rendererSettings.texture ??
495
- null);
496
- const resolvedBlendMode =
497
- overrides.blendMode ??
498
- (typeof material?.alphaMode === 'number' ? material.alphaMode : this.rendererSettings.materialBlendMode);
499
- const resolvedTransparent =
500
- overrides.transparent ??
501
- (typeof material?.needAlphaBlending === 'function'
502
- ? material.needAlphaBlending()
503
- : typeof material?.alpha === 'number'
504
- ? material.alpha < 1
505
- : this.rendererSettings.materialTransparent);
506
- const resolvedDepthTest =
507
- overrides.depthTest ??
508
- (typeof material?.disableDepthTest === 'boolean'
509
- ? !material.disableDepthTest
510
- : this.rendererSettings.materialDepthTest);
511
- const resolvedDepthWrite =
512
- overrides.depthWrite ??
513
- (typeof material?.disableDepthWrite === 'boolean'
514
- ? !material.disableDepthWrite
515
- : typeof material?.forceDepthWrite === 'boolean'
516
- ? material.forceDepthWrite
517
- : this.rendererSettings.materialDepthWrite);
518
- const resolvedAlphaTest =
519
- overrides.alphaTest ??
520
- (typeof material?.alphaCutOff === 'number'
521
- ? material.alphaCutOff
522
- : typeof material?.alphaCutOffValue === 'number'
523
- ? material.alphaCutOffValue
524
- : this.rendererSettings.materialAlphaTest);
525
-
526
- this.rendererSettings.texture = resolvedTexture;
527
- this.rendererSettings.materialBlendMode = resolvedBlendMode;
528
- this.rendererSettings.materialTransparent = resolvedTransparent;
529
- this.rendererSettings.materialDepthTest = resolvedDepthTest;
530
- this.rendererSettings.materialDepthWrite = resolvedDepthWrite;
531
- this.rendererSettings.materialAlphaTest = resolvedAlphaTest;
532
- if (overrides.layerMask !== undefined) {
533
- this.rendererSettings.layerMask = overrides.layerMask;
534
- }
535
- this.neededToUpdateRender = true;
536
- }
537
-
538
- pause() { this.paused = true; }
539
- play() { this.paused = false; }
540
- stop() { this.restart(); this.pause(); }
541
-
542
- private refreshTrailHistoryMode() {
543
- this.useFastTrailHistory = !this.behaviors.some((behavior) => (behavior as any).type === 'WidthOverLength');
544
- }
545
-
546
- private ensureTrailHistoryCapacity(particle: TrailParticle, capacity: number) {
547
- const trailAny = particle as any;
548
- if (trailAny._trailHistoryCapacity === capacity && trailAny._trailHistoryPositions) {
549
- return;
550
- }
551
- trailAny._trailHistoryCapacity = capacity;
552
- trailAny._trailHistoryPositions = new Float32Array(capacity * 3);
553
- trailAny._trailHistorySizes = new Float32Array(capacity);
554
- trailAny._trailHistoryColors = new Float32Array(capacity * 4);
555
- trailAny._trailHistoryHead = 0;
556
- trailAny._trailHistoryCount = 0;
557
- }
558
-
559
- private resetTrailHistory(particle: TrailParticle) {
560
- const trailAny = particle as any;
561
- trailAny._trailHistoryHead = 0;
562
- trailAny._trailHistoryCount = 0;
563
- }
564
-
565
- private getTrailHistoryCount(particle: TrailParticle): number {
566
- const trailAny = particle as any;
567
- const fastHistoryCount = trailAny._trailHistoryCount;
568
- if (typeof fastHistoryCount === 'number') {
569
- return fastHistoryCount;
570
- }
571
- return particle.previous.length;
572
- }
573
-
574
- private updateFastTrailHistory(particle: TrailParticle) {
575
- const trailAny = particle as any;
576
- const capacity = Math.max(1, Math.ceil(particle.length));
577
- this.ensureTrailHistoryCapacity(particle, capacity);
578
- let head = trailAny._trailHistoryHead as number;
579
- let count = trailAny._trailHistoryCount as number;
580
- const positions = trailAny._trailHistoryPositions as Float32Array;
581
- const sizes = trailAny._trailHistorySizes as Float32Array;
582
- const colors = trailAny._trailHistoryColors as Float32Array;
583
-
584
- if (particle.age <= particle.life) {
585
- const posIndex = head * 3;
586
- positions[posIndex] = particle.position.x;
587
- positions[posIndex + 1] = particle.position.y;
588
- positions[posIndex + 2] = particle.position.z;
589
- sizes[head] = particle.size.x;
590
- const colorIndex = head * 4;
591
- colors[colorIndex] = particle.color.x;
592
- colors[colorIndex + 1] = particle.color.y;
593
- colors[colorIndex + 2] = particle.color.z;
594
- colors[colorIndex + 3] = particle.color.w;
595
- head = (head + 1) % capacity;
596
- if (count < capacity) {
597
- count++;
598
- }
599
- } else if (count > 0) {
600
- count--;
601
- }
602
-
603
- trailAny._trailHistoryHead = head;
604
- trailAny._trailHistoryCount = count;
605
- }
606
-
607
- setQualityFactor(qualityFactor: number) {
608
- this.qualityFactor = Math.max(0.1, Math.min(1, qualityFactor));
609
- }
610
-
611
- private spawn(count: number, emissionState: EmissionState, matrix: Matrix4) {
612
- tempQ.setFromRotationMatrix(matrix);
613
- const translation = tempV;
614
- const scale = tempV2;
615
- matrix.decompose(translation, tempQ, scale);
616
- const behaviors = this.behaviors;
617
- const behaviorCount = behaviors.length;
618
- const renderMode = this.rendererSettings.renderMode;
619
- const isTrailMode = renderMode === RenderMode.Trail;
620
- const timeRatio = emissionState.time / this.duration;
621
- const trailSettings = isTrailMode ? (this.rendererEmitterSettings as TrailSettings) : undefined;
622
- const followLocalOrigin = Boolean(trailSettings?.followLocalOrigin);
623
-
624
- for (let i = 0; i < count; i++) {
625
- emissionState.burstParticleIndex = i;
626
- this.particleNum++;
627
- if (this.particles.length < this.particleNum) {
628
- if (isTrailMode) {
629
- this.particles.push(new TrailParticle());
630
- } else {
631
- this.particles.push(new SpriteParticle());
632
- }
633
- }
634
- const particle = this.particles[this.particleNum - 1];
635
- particle.reset();
636
- particle.speedModifier = 1;
637
- this.startColor.startGen(particle.memory);
638
- this.startColor.genColor(particle.memory, particle.startColor, this.emissionState.time);
639
- particle.color.copy(particle.startColor);
640
- this.startSpeed.startGen(particle.memory);
641
- particle.startSpeed = this.startSpeed.genValue(particle.memory, timeRatio);
642
- this.startLife.startGen(particle.memory);
643
- particle.life = this.startLife.genValue(particle.memory, timeRatio);
644
- particle.age = 0;
645
- this.startSize.startGen(particle.memory);
646
- if (this.startSize.type === 'vec3function') {
647
- (this.startSize as Vector3Generator).genValue(particle.memory, particle.startSize, timeRatio);
648
- } else {
649
- const size = (this.startSize as FunctionValueGenerator).genValue(particle.memory, timeRatio);
650
- particle.startSize.set(size, size, size);
651
- }
652
- this.startTileIndex.startGen(particle.memory);
653
- particle.uvTile = this.startTileIndex.genValue(particle.memory);
654
- particle.size.copy(particle.startSize);
655
-
656
- if (
657
- this.rendererSettings.renderMode === RenderMode.Mesh ||
658
- this.rendererSettings.renderMode === RenderMode.BillBoard ||
659
- this.rendererSettings.renderMode === RenderMode.VerticalBillBoard ||
660
- this.rendererSettings.renderMode === RenderMode.HorizontalBillBoard ||
661
- this.rendererSettings.renderMode === RenderMode.StretchedBillBoard
662
- ) {
663
- const sprite = particle as SpriteParticle;
664
- this.startRotation.startGen(particle.memory);
665
- if (renderMode === RenderMode.Mesh) {
666
- if (!(sprite.rotation instanceof Quaternion)) {
667
- sprite.rotation = new Quaternion();
668
- }
669
- if (this.startRotation.type === 'rotation') {
670
- this.startRotation.genValue(particle.memory, sprite.rotation as Quaternion, 1, timeRatio);
671
- } else {
672
- (sprite.rotation as Quaternion).setFromAxisAngle(UP, this.startRotation.genValue(sprite.memory, timeRatio));
673
- }
674
- } else {
675
- if (this.startRotation.type === 'rotation') {
676
- sprite.rotation = 0;
677
- } else {
678
- sprite.rotation = this.startRotation.genValue(sprite.memory, timeRatio);
679
- }
680
- }
681
- } else if (isTrailMode && trailSettings) {
682
- const trail = particle as TrailParticle;
683
- trailSettings.startLength.startGen(trail.memory);
684
- trail.length = trailSettings.startLength.genValue(trail.memory, timeRatio);
685
- if (this.useFastTrailHistory) {
686
- this.ensureTrailHistoryCapacity(trail, Math.max(1, Math.ceil(trail.length)));
687
- this.resetTrailHistory(trail);
688
- }
689
- }
690
-
691
- this.emitterShape.initialize(particle, emissionState);
692
-
693
- if (isTrailMode && followLocalOrigin) {
694
- const trail = particle as TrailParticle;
695
- if (!trail.localPosition) {
696
- trail.localPosition = new Vector3();
697
- }
698
- trail.localPosition.copy(trail.position);
699
- }
700
-
701
- if (this.worldSpace) {
702
- particle.position.applyMatrix4(matrix);
703
- particle.startSize.multiply(scale).abs();
704
- particle.size.copy(particle.startSize);
705
- particle.velocity.multiply(scale).applyMatrix3(this.normalMatrix);
706
- if (particle.rotation && particle.rotation instanceof Quaternion) {
707
- particle.rotation.multiplyQuaternions(tempQ, particle.rotation);
708
- }
709
- } else {
710
- if (this.onlyUsedByOther) {
711
- particle.parentMatrix = matrix;
712
- }
713
- }
714
-
715
- for (let j = 0; j < behaviorCount; j++) {
716
- behaviors[j].initialize(particle, this);
717
- }
718
- }
719
- }
720
-
721
- endEmit() {
722
- this.emitEnded = true;
723
- if (this.autoDestroy) {
724
- this.markForDestroy = true;
725
- }
726
- this.fire({type: 'emitEnd', particleSystem: this});
727
- }
728
-
729
- dispose() {
730
- if (this._renderer) this._renderer.deleteSystem(this);
731
- this.emitter.dispose();
732
- this.fire({type: 'destroy', particleSystem: this});
733
- }
734
-
735
- restart() {
736
- this.memory.length = 0;
737
- this.paused = false;
738
- this.particleNum = 0;
739
- this.emissionState.isBursting = false;
740
- this.emissionState.burstIndex = 0;
741
- this.emissionState.burstWaveIndex = 0;
742
- this.emissionState.time = 0;
743
- this.emissionState.waitEmiting = 0;
744
- this.behaviors.forEach((behavior) => behavior.reset());
745
- this.emitEnded = false;
746
- this.markForDestroy = false;
747
- this.prewarmed = false;
748
- this.emissionBursts.forEach((burst) => burst.count.startGen(this.memory));
749
- this.emissionOverDistance.startGen(this.memory);
750
- }
751
-
752
- private firstTimeUpdate = true;
753
-
754
- /** @internal */
755
- update(delta: number) {
756
- if (this.paused) return;
757
-
758
- if (this.firstTimeUpdate) {
759
- this.firstTimeUpdate = false;
760
- this.emitter.computeWorldMatrix(true);
761
- }
762
-
763
- if (this.emitEnded && this.particleNum === 0) {
764
- if (this.markForDestroy) this.dispose();
765
- return;
766
- }
767
-
768
- if (this.looping && this.prewarm && !this.prewarmed) {
769
- this.prewarmed = true;
770
- for (let i = 0; i < this.duration * PREWARM_FPS; i++) {
771
- this.update(1.0 / PREWARM_FPS);
772
- }
773
- }
774
-
775
- if (delta > 0.1) {
776
- delta = 0.1;
777
- }
778
-
779
- if (this.neededToUpdateRender) {
780
- if (this._renderer) this._renderer.updateSystem(this);
781
- this.neededToUpdateRender = false;
782
- }
783
-
784
- if (!this.onlyUsedByOther) {
785
- this.emit(delta, this.emissionState, this.emitter.matrixWorld);
786
- }
787
-
788
- const particles = this.particles;
789
- const behaviorCount = this.behaviors.length;
790
- const particleCount = this.particleNum;
791
- this.emitterShape.update(this, delta);
792
- for (let j = 0; j < behaviorCount; j++) {
793
- const behavior = this.behaviors[j];
794
- behavior.frameUpdate(delta);
795
- for (let i = 0; i < particleCount; i++) {
796
- const particle = particles[i];
797
- if (!particle.died) {
798
- behavior.update(particle, delta);
799
- }
800
- }
801
- }
802
-
803
- const followLocalOrigin =
804
- this.rendererSettings.renderMode === RenderMode.Trail &&
805
- (this.rendererEmitterSettings as TrailSettings).followLocalOrigin;
806
- const emitterMatrix = this.emitter.matrixWorld;
807
- for (let i = 0; i < particleCount; i++) {
808
- const particle = particles[i];
809
- if (followLocalOrigin && (particle as TrailParticle).localPosition) {
810
- particle.position.copy((particle as TrailParticle).localPosition!);
811
- if (particle.parentMatrix) {
812
- particle.position.applyMatrix4(particle.parentMatrix);
813
- } else {
814
- particle.position.applyMatrix4(emitterMatrix);
815
- }
816
- } else {
817
- particle.position.addScaledVector(particle.velocity, delta * particle.speedModifier);
818
- }
819
- particle.age += delta;
820
- }
821
-
822
- if (this.rendererSettings.renderMode === RenderMode.Trail) {
823
- for (let i = 0; i < particleCount; i++) {
824
- const trailParticle = particles[i] as TrailParticle;
825
- if (this.useFastTrailHistory) {
826
- this.updateFastTrailHistory(trailParticle);
827
- } else {
828
- trailParticle.update();
829
- }
830
- }
831
- }
832
-
833
- let liveParticleCount = this.particleNum;
834
- for (let i = 0; i < liveParticleCount; i++) {
835
- const particle = particles[i];
836
- if (
837
- particle.died &&
838
- (!(particle instanceof TrailParticle) || this.getTrailHistoryCount(particle as TrailParticle) === 0)
839
- ) {
840
- particles[i] = particles[liveParticleCount - 1];
841
- particles[liveParticleCount - 1] = particle;
842
- liveParticleCount--;
843
- i--;
844
- this.fire({type: 'particleDied', particleSystem: this, particle: particle});
845
- }
846
- }
847
- this.particleNum = liveParticleCount;
848
- }
849
-
850
- emit(delta: number, emissionState: EmissionState, emitterMatrix: Matrix4) {
851
- if (emissionState.time > this.duration) {
852
- if (this.looping) {
853
- emissionState.time -= this.duration;
854
- emissionState.burstIndex = 0;
855
- this.behaviors.forEach((behavior) => behavior.reset());
856
- } else {
857
- if (!this.emitEnded && !this.onlyUsedByOther) {
858
- this.endEmit();
859
- }
860
- }
861
- }
862
-
863
- this.normalMatrix.getNormalMatrix(emitterMatrix);
864
- const emissionBursts = this.emissionBursts;
865
- const emissionBurstCount = emissionBursts.length;
866
- const qualityFactor = this.qualityFactor;
867
-
868
- const totalSpawn = Math.ceil(emissionState.waitEmiting);
869
- this.spawn(totalSpawn, emissionState, emitterMatrix);
870
- emissionState.waitEmiting -= totalSpawn;
871
-
872
- while (
873
- emissionState.burstIndex < emissionBurstCount &&
874
- emissionBursts[emissionState.burstIndex].time <= emissionState.time
875
- ) {
876
- const burst = emissionBursts[emissionState.burstIndex];
877
- if (Math.random() < burst.probability) {
878
- const rawCount = burst.count.genValue(this.memory, this.time);
879
- const count = qualityFactor >= 0.999 ? rawCount : Math.floor(rawCount * qualityFactor);
880
- emissionState.isBursting = true;
881
- emissionState.burstParticleCount = count;
882
- this.spawn(count, emissionState, emitterMatrix);
883
- emissionState.isBursting = false;
884
- }
885
- emissionState.burstIndex++;
886
- }
887
-
888
- if (!this.emitEnded) {
889
- const timeRatio = emissionState.time / this.duration;
890
- emissionState.waitEmiting += delta * this.emissionOverTime.genValue(this.memory, timeRatio) * qualityFactor;
891
-
892
- const previousWorldPos = emissionState.previousWorldPos;
893
- if (previousWorldPos !== undefined) {
894
- this.temp.set(emitterMatrix.elements[12], emitterMatrix.elements[13], emitterMatrix.elements[14]);
895
- emissionState.travelDistance += previousWorldPos.distanceTo(this.temp);
896
- const emitPerMeter = this.emissionOverDistance.genValue(this.memory, timeRatio) * qualityFactor;
897
- if (emissionState.travelDistance * emitPerMeter > 0) {
898
- const count = Math.floor(emissionState.travelDistance * emitPerMeter);
899
- emissionState.travelDistance -= count / emitPerMeter;
900
- emissionState.waitEmiting += count;
901
- }
902
- }
903
- }
904
- if (emissionState.previousWorldPos === undefined) emissionState.previousWorldPos = new Vector3();
905
- emissionState.previousWorldPos.set(emitterMatrix.elements[12], emitterMatrix.elements[13], emitterMatrix.elements[14]);
906
- emissionState.time += delta;
907
- }
908
-
909
- toJSON(metaData?: BabylonMetaData | string, _options: SerializationOptions = {}): ParticleSystemJSONParameters {
910
- const isRootObject = metaData === undefined || typeof metaData === 'string';
911
- const meta: BabylonMetaData = isRootObject
912
- ? {
913
- geometries: {},
914
- materials: {},
915
- textures: {},
916
- images: {},
917
- shapes: {},
918
- skeletons: {},
919
- animations: {},
920
- nodes: {},
921
- }
922
- : metaData;
923
-
924
- const geometryUUID = this.ensureGeometryMeta(meta);
925
- const materialUUID = this.ensureMaterialMeta(meta);
926
- const rendererEmitterSettingsJSON = this.toRendererSettingsJSON();
927
-
928
- return {
929
- version: '3.0',
930
- autoDestroy: this.autoDestroy,
931
- looping: this.looping,
932
- prewarm: this.prewarm,
933
- duration: this.duration,
934
- shape: this.emitterShape.toJSON(),
935
- startLife: this.startLife.toJSON(),
936
- startSpeed: this.startSpeed.toJSON(),
937
- startRotation: this.startRotation.toJSON(),
938
- startSize: this.startSize.toJSON(),
939
- startColor: this.startColor.toJSON(),
940
- emissionOverTime: this.emissionOverTime.toJSON(),
941
- emissionOverDistance: this.emissionOverDistance.toJSON(),
942
- emissionBursts: this.emissionBursts.map((burst) => ({
943
- time: burst.time,
944
- count: burst.count.toJSON(),
945
- probability: burst.probability,
946
- interval: burst.interval,
947
- cycle: burst.cycle,
948
- })),
949
- onlyUsedByOther: this.onlyUsedByOther,
950
- instancingGeometry: geometryUUID,
951
- renderMode: this.renderMode,
952
- renderOrder: this.renderOrder,
953
- rendererEmitterSettings: rendererEmitterSettingsJSON,
954
- material: materialUUID,
955
- layers: this.rendererSettings.layerMask,
956
- startTileIndex: this.startTileIndex.toJSON(),
957
- uTileCount: this.rendererSettings.uTileCount,
958
- vTileCount: this.rendererSettings.vTileCount,
959
- blendTiles: this.rendererSettings.blendTiles,
960
- softParticles: this.rendererSettings.softParticles,
961
- softFarFade: this.rendererSettings.softFarFade,
962
- softNearFade: this.rendererSettings.softNearFade,
963
- blending: this.rendererSettings.materialBlendMode,
964
- transparent: this.rendererSettings.materialTransparent,
965
- depthTest: this.rendererSettings.materialDepthTest,
966
- depthWrite: this.rendererSettings.materialDepthWrite,
967
- alphaTest: this.rendererSettings.materialAlphaTest,
968
- behaviors: this.behaviors.map((b) => b.toJSON()),
969
- worldSpace: this.worldSpace,
970
- };
971
- }
972
-
973
- static fromJSON(
974
- json: ParticleSystemJSONParameters,
975
- meta: BabylonMetaData,
976
- dependencies: {[uuid: string]: Behavior} = {},
977
- scene?: Scene
978
- ): ParticleSystem {
979
- const shape = EmitterFromJSON(json.shape, meta as any);
980
- let rendererEmitterSettings: RendererEmitterSettings;
981
- if (json.renderMode === RenderMode.Trail) {
982
- const trailSettings = json.rendererEmitterSettings as TrailSettings;
983
- rendererEmitterSettings = {
984
- startLength:
985
- trailSettings?.startLength !== undefined
986
- ? ValueGeneratorFromJSON(trailSettings.startLength)
987
- : new ConstantValue(30),
988
- followLocalOrigin: trailSettings?.followLocalOrigin ?? false,
989
- };
990
- } else if (json.renderMode === RenderMode.StretchedBillBoard) {
991
- rendererEmitterSettings = {...(json.rendererEmitterSettings ?? {})};
992
- if (json.speedFactor !== undefined) {
993
- (rendererEmitterSettings as StretchedBillBoardSettings).speedFactor = json.speedFactor;
994
- }
995
- } else {
996
- rendererEmitterSettings = {};
997
- }
998
-
999
- const materialMeta = json.material ? meta.materials?.[json.material] : undefined;
1000
- const materialTextureRef = materialMeta?.texture ?? json.texture;
1001
- const texture =
1002
- typeof materialTextureRef === 'string'
1003
- ? meta.textures?.[materialTextureRef] ?? null
1004
- : materialTextureRef ?? null;
1005
- const resolvedGeometryEntry =
1006
- typeof json.instancingGeometry === 'string'
1007
- ? meta.geometries?.[json.instancingGeometry]
1008
- : json.instancingGeometry;
1009
- const resolvedGeometry = ParticleSystem.resolveGeometryData(resolvedGeometryEntry);
1010
-
1011
- const ps = new ParticleSystem({
1012
- scene,
1013
- autoDestroy: json.autoDestroy,
1014
- looping: json.looping,
1015
- prewarm: json.prewarm,
1016
- duration: json.duration,
1017
- shape,
1018
- startLife: ValueGeneratorFromJSON(json.startLife),
1019
- startSpeed: ValueGeneratorFromJSON(json.startSpeed),
1020
- startRotation: GeneratorFromJSON(json.startRotation) as RotationGenerator | ValueGenerator | FunctionValueGenerator,
1021
- startSize: GeneratorFromJSON(json.startSize) as Vector3Generator | ValueGenerator | FunctionValueGenerator,
1022
- startColor: ColorGeneratorFromJSON(json.startColor) as ColorGenerator,
1023
- emissionOverTime: ValueGeneratorFromJSON(json.emissionOverTime),
1024
- emissionOverDistance: ValueGeneratorFromJSON(json.emissionOverDistance),
1025
- emissionBursts: json.emissionBursts?.map((burst: any) => ({
1026
- time: burst.time,
1027
- count: typeof burst.count === 'number' ? new ConstantValue(burst.count) : ValueGeneratorFromJSON(burst.count),
1028
- probability: burst.probability ?? 1,
1029
- interval: burst.interval ?? 0.1,
1030
- cycle: burst.cycle ?? burst.cycleCount ?? 1,
1031
- })),
1032
- onlyUsedByOther: json.onlyUsedByOther,
1033
- instancingGeometry: resolvedGeometry.positions,
1034
- instancingIndices: resolvedGeometry.indices,
1035
- instancingUVs: resolvedGeometry.uvs,
1036
- instancingNormals: resolvedGeometry.normals,
1037
- renderMode: json.renderMode,
1038
- rendererEmitterSettings,
1039
- renderOrder: json.renderOrder,
1040
- texture,
1041
- material: materialMeta?.sourceMaterial,
1042
- blendMode: materialMeta?.alphaMode ?? json.blending ?? Constants.ALPHA_ADD,
1043
- transparent: materialMeta?.transparent ?? json.transparent ?? true,
1044
- depthTest: materialMeta?.depthTest ?? json.depthTest ?? true,
1045
- depthWrite: materialMeta?.depthWrite ?? json.depthWrite ?? false,
1046
- alphaTest: materialMeta?.alphaTest ?? json.alphaTest ?? 0,
1047
- startTileIndex:
1048
- typeof json.startTileIndex === 'number'
1049
- ? new ConstantValue(json.startTileIndex)
1050
- : (ValueGeneratorFromJSON(json.startTileIndex) as ValueGenerator),
1051
- uTileCount: json.uTileCount,
1052
- vTileCount: json.vTileCount,
1053
- blendTiles: json.blendTiles,
1054
- softParticles: json.softParticles,
1055
- softFarFade: json.softFarFade,
1056
- softNearFade: json.softNearFade,
1057
- behaviors: [],
1058
- worldSpace: json.worldSpace,
1059
- layerMask: json.layers,
1060
- });
1061
- ps.behaviors = (json.behaviors ?? [])
1062
- .map((behaviorJson) => {
1063
- const behavior = BehaviorFromJSON(behaviorJson, ps);
1064
- if (behavior && behavior.type === 'EmitSubParticleSystem') {
1065
- dependencies[(behaviorJson as any).subParticleSystem] = behavior;
1066
- }
1067
- return behavior;
1068
- })
1069
- .filter((behavior) => behavior !== null) as Behavior[];
1070
- return ps;
1071
- }
1072
-
1073
- private static nextSerializationId(prefix: string): string {
1074
- ParticleSystem.serializationCounter += 1;
1075
- return `${prefix}_${ParticleSystem.serializationCounter}`;
1076
- }
1077
-
1078
- private static createFallbackNormals(positions: Float32Array): Float32Array {
1079
- const normals = new Float32Array(positions.length);
1080
- for (let i = 0; i < normals.length; i += 3) {
1081
- normals[i] = 0;
1082
- normals[i + 1] = 0;
1083
- normals[i + 2] = 1;
1084
- }
1085
- return normals;
1086
- }
1087
-
1088
- private toRendererSettingsJSON(): RendererEmitterSettings {
1089
- if (this.renderMode === RenderMode.Trail) {
1090
- return {
1091
- startLength: (this.rendererEmitterSettings as TrailSettings).startLength.toJSON(),
1092
- followLocalOrigin: (this.rendererEmitterSettings as TrailSettings).followLocalOrigin,
1093
- };
1094
- }
1095
- if (this.renderMode === RenderMode.StretchedBillBoard) {
1096
- return {
1097
- speedFactor: (this.rendererEmitterSettings as StretchedBillBoardSettings).speedFactor,
1098
- lengthFactor: (this.rendererEmitterSettings as StretchedBillBoardSettings).lengthFactor,
1099
- };
1100
- }
1101
- return {};
1102
- }
1103
-
1104
- private ensureGeometryMeta(meta: BabylonMetaData): string {
1105
- const geometryUUID = ParticleSystem.nextSerializationId('quarks_geometry');
1106
- meta.geometries[geometryUUID] = {
1107
- uuid: geometryUUID,
1108
- type: 'QuarksGeometry',
1109
- positions: Array.from(this.rendererSettings.instancingGeometry),
1110
- indices: Array.from(this.rendererSettings.instancingIndices),
1111
- uvs: this.rendererSettings.instancingUVs ? Array.from(this.rendererSettings.instancingUVs) : undefined,
1112
- normals: this.rendererSettings.instancingNormals ? Array.from(this.rendererSettings.instancingNormals) : undefined,
1113
- };
1114
- return geometryUUID;
1115
- }
1116
-
1117
- private ensureMaterialMeta(meta: BabylonMetaData): string {
1118
- const texture = this.rendererSettings.texture;
1119
- let textureUUID: string | undefined;
1120
- if (texture) {
1121
- textureUUID = ParticleSystem.nextSerializationId('quarks_texture');
1122
- meta.textures[textureUUID] = texture;
1123
- }
1124
-
1125
- const materialUUID = ParticleSystem.nextSerializationId('quarks_material');
1126
- meta.materials[materialUUID] = {
1127
- uuid: materialUUID,
1128
- type: 'QuarksMaterial',
1129
- transparent: this.rendererSettings.materialTransparent,
1130
- alphaMode: this.rendererSettings.materialBlendMode,
1131
- depthTest: this.rendererSettings.materialDepthTest,
1132
- depthWrite: this.rendererSettings.materialDepthWrite,
1133
- alphaTest: this.rendererSettings.materialAlphaTest,
1134
- texture: textureUUID,
1135
- sourceMaterial: this.materialRef ?? undefined,
1136
- };
1137
- return materialUUID;
1138
- }
1139
-
1140
- private static resolveGeometryData(entry: any): {
1141
- positions: Float32Array;
1142
- indices: Uint32Array | Uint16Array;
1143
- uvs?: Float32Array;
1144
- normals?: Float32Array;
1145
- } {
1146
- if (entry?.positions) {
1147
- const positions =
1148
- entry.positions instanceof Float32Array ? entry.positions : new Float32Array(entry.positions);
1149
- const rawIndices =
1150
- entry.indices instanceof Uint16Array || entry.indices instanceof Uint32Array
1151
- ? entry.indices
1152
- : entry.indices
1153
- ? new Uint32Array(entry.indices)
1154
- : undefined;
1155
- return {
1156
- positions,
1157
- indices: ensureTriangleIndices(positions, rawIndices),
1158
- uvs: entry.uvs
1159
- ? entry.uvs instanceof Float32Array
1160
- ? entry.uvs
1161
- : new Float32Array(entry.uvs)
1162
- : undefined,
1163
- normals: entry.normals
1164
- ? entry.normals instanceof Float32Array
1165
- ? entry.normals
1166
- : new Float32Array(entry.normals)
1167
- : undefined,
1168
- };
1169
- }
1170
-
1171
- const data = entry?.data ?? entry;
1172
- if (data?.attributes?.position?.array) {
1173
- const positions = new Float32Array(data.attributes.position.array);
1174
- let indices: Uint32Array | Uint16Array | undefined;
1175
- if (data.index?.array) {
1176
- const indexType = data.index.type === 'Uint16Array' ? Uint16Array : Uint32Array;
1177
- indices = new indexType(data.index.array);
1178
- }
1179
- return {
1180
- positions,
1181
- indices: ensureTriangleIndices(positions, indices),
1182
- uvs: data.attributes.uv?.array ? new Float32Array(data.attributes.uv.array) : undefined,
1183
- normals: data.attributes.normal?.array ? new Float32Array(data.attributes.normal.array) : undefined,
1184
- };
1185
- }
1186
-
1187
- return {
1188
- positions: DEFAULT_POSITIONS,
1189
- indices: DEFAULT_INDICES,
1190
- uvs: DEFAULT_UVS,
1191
- };
1192
- }
1193
-
1194
- addBehavior(behavior: Behavior) {
1195
- this.behaviors.push(behavior);
1196
- this.refreshTrailHistoryMode();
1197
- }
1198
-
1199
- getRendererSettings(): VFXBatchSettings {
1200
- return this.rendererSettings;
1201
- }
1202
-
1203
- addEventListener(event: ParticleSystemEventType, callback: (event: ParticleSystemEvent) => void): void {
1204
- if (!this.listeners[event]) this.listeners[event] = [];
1205
- this.listeners[event].push(callback);
1206
- }
1207
-
1208
- removeAllEventListeners(event: ParticleSystemEventType): void {
1209
- if (this.listeners[event]) this.listeners[event] = [];
1210
- }
1211
-
1212
- removeEventListener(event: ParticleSystemEventType, callback: (event: ParticleSystemEvent) => void): void {
1213
- if (this.listeners[event]) {
1214
- const index = this.listeners[event].indexOf(callback);
1215
- if (index !== -1) this.listeners[event].splice(index, 1);
1216
- }
1217
- }
1218
-
1219
- private fire(event: ParticleSystemEvent) {
1220
- if (this.listeners[event.type]) {
1221
- this.listeners[event.type].forEach((callback) => callback(event));
1222
- }
1223
- }
1224
-
1225
- clone(): ParticleSystem {
1226
- const newBehaviors: Array<Behavior> = this.behaviors.map((b) => b.clone());
1227
- let rendererEmitterSettings: RendererEmitterSettings;
1228
-
1229
- if (this.renderMode === RenderMode.Trail) {
1230
- rendererEmitterSettings = {
1231
- startLength: (this.rendererEmitterSettings as TrailSettings).startLength.clone(),
1232
- followLocalOrigin: (this.rendererEmitterSettings as TrailSettings).followLocalOrigin,
1233
- };
1234
- } else if (this.renderMode === RenderMode.StretchedBillBoard) {
1235
- rendererEmitterSettings = {
1236
- lengthFactor: (this.rendererEmitterSettings as StretchedBillBoardSettings).lengthFactor,
1237
- speedFactor: (this.rendererEmitterSettings as StretchedBillBoardSettings).speedFactor,
1238
- };
1239
- } else {
1240
- rendererEmitterSettings = {};
1241
- }
1242
-
1243
- return new ParticleSystem({
1244
- autoDestroy: this.autoDestroy,
1245
- looping: this.looping,
1246
- prewarm: this.prewarm,
1247
- duration: this.duration,
1248
- shape: this.emitterShape.clone(),
1249
- startLife: this.startLife.clone(),
1250
- startSpeed: this.startSpeed.clone(),
1251
- startRotation: this.startRotation.clone(),
1252
- startSize: this.startSize.clone(),
1253
- startColor: this.startColor.clone(),
1254
- emissionOverTime: this.emissionOverTime.clone(),
1255
- emissionOverDistance: this.emissionOverDistance.clone(),
1256
- emissionBursts: this.emissionBursts.map((b) => ({...b})),
1257
- onlyUsedByOther: this.onlyUsedByOther,
1258
- instancingGeometry: this.rendererSettings.instancingGeometry,
1259
- instancingIndices: this.rendererSettings.instancingIndices,
1260
- instancingUVs: this.rendererSettings.instancingUVs,
1261
- instancingNormals: this.rendererSettings.instancingNormals,
1262
- renderMode: this.renderMode,
1263
- renderOrder: this.renderOrder,
1264
- rendererEmitterSettings,
1265
- material: this.materialRef,
1266
- texture: this.rendererSettings.texture,
1267
- startTileIndex: this.startTileIndex,
1268
- uTileCount: this.rendererSettings.uTileCount,
1269
- vTileCount: this.rendererSettings.vTileCount,
1270
- blendTiles: this.rendererSettings.blendTiles,
1271
- softParticles: this.rendererSettings.softParticles,
1272
- softFarFade: this.rendererSettings.softFarFade,
1273
- softNearFade: this.rendererSettings.softNearFade,
1274
- behaviors: newBehaviors,
1275
- worldSpace: this.worldSpace,
1276
- blendMode: this.rendererSettings.materialBlendMode,
1277
- transparent: this.rendererSettings.materialTransparent,
1278
- depthTest: this.rendererSettings.materialDepthTest,
1279
- depthWrite: this.rendererSettings.materialDepthWrite,
1280
- alphaTest: this.rendererSettings.materialAlphaTest,
1281
- layerMask: this.rendererSettings.layerMask,
1282
- scene: this.emitter.getScene(),
1283
- });
1284
- }
1285
- }
1
+ import {Scene} from '@babylonjs/core/scene';
2
+ import {Texture} from '@babylonjs/core/Materials/Textures/texture';
3
+ import {Constants} from '@babylonjs/core/Engines/constants';
4
+ import {
5
+ AxisAngleGenerator,
6
+ ColorGenerator,
7
+ ColorGeneratorFromJSON,
8
+ ConstantColor,
9
+ ConstantValue,
10
+ FunctionColorGenerator,
11
+ FunctionJSON,
12
+ FunctionValueGenerator,
13
+ GeneratorFromJSON,
14
+ ValueGenerator,
15
+ ValueGeneratorFromJSON,
16
+ Behavior,
17
+ BehaviorFromJSON,
18
+ Particle,
19
+ SpriteParticle,
20
+ TrailParticle,
21
+ EmitterFromJSON,
22
+ EmitterShape,
23
+ SphereEmitter,
24
+ RendererEmitterSettings,
25
+ RotationGenerator,
26
+ IParticleSystem,
27
+ EmissionState,
28
+ GeneratorMemory,
29
+ TrailSettings,
30
+ StretchedBillBoardSettings,
31
+ SerializationOptions,
32
+ Vector3,
33
+ Vector4,
34
+ Matrix3,
35
+ Matrix4,
36
+ Quaternion,
37
+ Vector3Generator,
38
+ ParticleSystemEvent,
39
+ ParticleSystemEventType,
40
+ } from 'quarks.core';
41
+ import {ParticleEmitter} from './ParticleEmitter';
42
+ import {RenderMode} from './VFXBatch';
43
+ import {BatchedRenderer, VFXBatchSettings} from './BatchedRenderer';
44
+ import {ensureTriangleIndices} from './geometryUtil';
45
+
46
+ export interface BurstParameters {
47
+ time: number;
48
+ count: ValueGenerator | FunctionValueGenerator;
49
+ cycle: number;
50
+ interval: number;
51
+ probability: number;
52
+ }
53
+
54
+ const UP = new Vector3(0, 0, 1);
55
+ const tempQ = new Quaternion();
56
+ const tempV = new Vector3();
57
+ const tempV2 = new Vector3();
58
+ const PREWARM_FPS = 60;
59
+
60
+ const DEFAULT_POSITIONS = new Float32Array([
61
+ -0.5, -0.5, 0,
62
+ 0.5, -0.5, 0,
63
+ 0.5, 0.5, 0,
64
+ -0.5, 0.5, 0,
65
+ ]);
66
+ const DEFAULT_UVS = new Float32Array([0, 0, 1, 0, 1, 1, 0, 1]);
67
+ const DEFAULT_INDICES = new Uint32Array([0, 1, 2, 0, 2, 3]);
68
+
69
+ export interface ParticleSystemParameters {
70
+ autoDestroy?: boolean;
71
+ looping?: boolean;
72
+ prewarm?: boolean;
73
+ duration?: number;
74
+ startDelay?: ValueGenerator | FunctionValueGenerator;
75
+ shape?: EmitterShape;
76
+ startLife?: ValueGenerator | FunctionValueGenerator;
77
+ startSpeed?: ValueGenerator | FunctionValueGenerator;
78
+ startRotation?: ValueGenerator | FunctionValueGenerator | RotationGenerator;
79
+ startSize?: ValueGenerator | FunctionValueGenerator | Vector3Generator;
80
+ startLength?: ValueGenerator | FunctionValueGenerator;
81
+ startColor?: ColorGenerator | FunctionColorGenerator;
82
+ emissionOverTime?: ValueGenerator | FunctionValueGenerator;
83
+ emissionOverDistance?: ValueGenerator | FunctionValueGenerator;
84
+ emissionBursts?: Array<BurstParameters>;
85
+ onlyUsedByOther?: boolean;
86
+ behaviors?: Array<Behavior>;
87
+ instancingGeometry?: Float32Array;
88
+ instancingIndices?: Uint32Array | Uint16Array;
89
+ instancingUVs?: Float32Array;
90
+ instancingNormals?: Float32Array;
91
+ renderMode?: RenderMode;
92
+ rendererEmitterSettings?: RendererEmitterSettings;
93
+ speedFactor?: number;
94
+ material?: any;
95
+ texture?: Texture | null;
96
+ startTileIndex?: ValueGenerator;
97
+ uTileCount?: number;
98
+ vTileCount?: number;
99
+ blendTiles?: boolean;
100
+ softParticles?: boolean;
101
+ softFarFade?: number;
102
+ softNearFade?: number;
103
+ renderOrder?: number;
104
+ worldSpace?: boolean;
105
+ blendMode?: number;
106
+ transparent?: boolean;
107
+ depthTest?: boolean;
108
+ depthWrite?: boolean;
109
+ alphaTest?: number;
110
+ layerMask?: number;
111
+ scene?: Scene;
112
+ }
113
+
114
+ export interface BurstParametersJSON {
115
+ time: number;
116
+ count: FunctionJSON | number;
117
+ cycle: number;
118
+ interval: number;
119
+ probability: number;
120
+ }
121
+
122
+ export interface ParticleSystemJSONParameters {
123
+ version: string;
124
+ autoDestroy: boolean;
125
+ looping: boolean;
126
+ prewarm: boolean;
127
+ duration: number;
128
+ startDelay?: FunctionJSON;
129
+ shape: any;
130
+ startLife: FunctionJSON;
131
+ startSpeed: FunctionJSON;
132
+ startRotation: FunctionJSON;
133
+ startSize: FunctionJSON;
134
+ startColor: FunctionJSON;
135
+ emissionOverTime: FunctionJSON;
136
+ emissionOverDistance: FunctionJSON;
137
+ emissionBursts?: Array<BurstParametersJSON>;
138
+ onlyUsedByOther: boolean;
139
+ rendererEmitterSettings: RendererEmitterSettings;
140
+ instancingGeometry?: any;
141
+ renderMode: number;
142
+ renderOrder?: number;
143
+ speedFactor?: number;
144
+ texture?: string;
145
+ material?: string;
146
+ layers?: number;
147
+ startTileIndex: FunctionJSON | number;
148
+ uTileCount: number;
149
+ vTileCount: number;
150
+ blendTiles?: boolean;
151
+ softParticles?: boolean;
152
+ softFarFade?: number;
153
+ softNearFade?: number;
154
+ blending?: number;
155
+ transparent?: boolean;
156
+ depthTest?: boolean;
157
+ depthWrite?: boolean;
158
+ alphaTest?: number;
159
+ behaviors: Array<any>;
160
+ worldSpace: boolean;
161
+ }
162
+
163
+ export interface BabylonMetaData {
164
+ textures: {[uuid: string]: Texture | null};
165
+ materials: {[uuid: string]: any};
166
+ geometries: {[uuid: string]: any};
167
+ images?: {[uuid: string]: any};
168
+ shapes?: {[uuid: string]: any};
169
+ skeletons?: {[uuid: string]: any};
170
+ animations?: {[uuid: string]: any};
171
+ nodes?: {[uuid: string]: any};
172
+ }
173
+
174
+ export class ParticleSystem implements IParticleSystem {
175
+ private static serializationCounter = 0;
176
+
177
+ autoDestroy: boolean;
178
+ prewarm: boolean;
179
+ looping: boolean;
180
+ duration: number;
181
+ startDelay: ValueGenerator | FunctionValueGenerator;
182
+ startLife: ValueGenerator | FunctionValueGenerator;
183
+ startSpeed: ValueGenerator | FunctionValueGenerator;
184
+ startRotation: ValueGenerator | FunctionValueGenerator | RotationGenerator;
185
+ startSize: ValueGenerator | FunctionValueGenerator | Vector3Generator;
186
+ startColor: ColorGenerator | FunctionColorGenerator;
187
+ startTileIndex: ValueGenerator;
188
+ rendererEmitterSettings: RendererEmitterSettings;
189
+ emissionOverTime: ValueGenerator | FunctionValueGenerator;
190
+ emissionOverDistance: ValueGenerator | FunctionValueGenerator;
191
+ emissionBursts: Array<BurstParameters>;
192
+ onlyUsedByOther: boolean;
193
+ worldSpace: boolean;
194
+ particleNum: number;
195
+ paused: boolean;
196
+ particles: Array<Particle>;
197
+ emitterShape: EmitterShape;
198
+ emitter: ParticleEmitter;
199
+ rendererSettings: VFXBatchSettings;
200
+ neededToUpdateRender: boolean;
201
+ behaviors: Array<Behavior>;
202
+ emissionState: EmissionState;
203
+ emitterVelocity: Vector3 = new Vector3();
204
+
205
+ private prewarmed: boolean;
206
+ private emitEnded: boolean;
207
+ private markForDestroy: boolean;
208
+ private startDelayTimeLeft = 0;
209
+ private previousEmitterPos?: Vector3;
210
+ private tempEmitterPos: Vector3 = new Vector3();
211
+ private temp: Vector3 = new Vector3();
212
+ private normalMatrix: Matrix3 = new Matrix3();
213
+ private memory: GeneratorMemory = [];
214
+ private listeners: {[event: string]: Array<(event: ParticleSystemEvent) => void>} = {};
215
+ private readonly layerMaskProxy: {mask: number};
216
+ private materialRef: any = null;
217
+ private qualityFactor = 1;
218
+ private useFastTrailHistory = true;
219
+ /** @internal **/
220
+ _renderer?: BatchedRenderer;
221
+
222
+ set time(time: number) {
223
+ this.emissionState.time = time;
224
+ }
225
+
226
+ get time(): number {
227
+ return this.emissionState.time;
228
+ }
229
+
230
+ get layers() {
231
+ return this.layerMaskProxy;
232
+ }
233
+
234
+ get texture() {
235
+ return this.rendererSettings.texture;
236
+ }
237
+
238
+ set texture(texture: Texture | null) {
239
+ this.rendererSettings.texture = texture;
240
+ this.neededToUpdateRender = true;
241
+ }
242
+
243
+ get material() {
244
+ return this.materialRef;
245
+ }
246
+
247
+ set material(material: any) {
248
+ this.materialRef = material;
249
+ this.applyMaterialSettings(material);
250
+ }
251
+
252
+ get uTileCount() {
253
+ return this.rendererSettings.uTileCount;
254
+ }
255
+
256
+ set uTileCount(u: number) {
257
+ this.rendererSettings.uTileCount = u;
258
+ this.neededToUpdateRender = true;
259
+ }
260
+
261
+ get vTileCount() {
262
+ return this.rendererSettings.vTileCount;
263
+ }
264
+
265
+ set vTileCount(v: number) {
266
+ this.rendererSettings.vTileCount = v;
267
+ this.neededToUpdateRender = true;
268
+ }
269
+
270
+ get blendTiles() {
271
+ return this.rendererSettings.blendTiles;
272
+ }
273
+
274
+ set blendTiles(v: boolean) {
275
+ this.rendererSettings.blendTiles = v;
276
+ this.neededToUpdateRender = true;
277
+ }
278
+
279
+ get softParticles() {
280
+ return this.rendererSettings.softParticles;
281
+ }
282
+
283
+ set softParticles(v: boolean) {
284
+ this.rendererSettings.softParticles = v;
285
+ this.neededToUpdateRender = true;
286
+ }
287
+
288
+ get softNearFade() {
289
+ return this.rendererSettings.softNearFade;
290
+ }
291
+
292
+ set softNearFade(v: number) {
293
+ this.rendererSettings.softNearFade = v;
294
+ this.neededToUpdateRender = true;
295
+ }
296
+
297
+ get softFarFade() {
298
+ return this.rendererSettings.softFarFade;
299
+ }
300
+
301
+ set softFarFade(v: number) {
302
+ this.rendererSettings.softFarFade = v;
303
+ this.neededToUpdateRender = true;
304
+ }
305
+
306
+ get instancingGeometry(): Float32Array {
307
+ return this.rendererSettings.instancingGeometry;
308
+ }
309
+
310
+ set instancingGeometry(geometry: Float32Array) {
311
+ this.restart();
312
+ this.particles.length = 0;
313
+ this.rendererSettings.instancingGeometry = geometry;
314
+ this.neededToUpdateRender = true;
315
+ }
316
+
317
+ get blending() {
318
+ return this.rendererSettings.materialBlendMode;
319
+ }
320
+
321
+ set blending(blending: number) {
322
+ this.rendererSettings.materialBlendMode = blending;
323
+ this.neededToUpdateRender = true;
324
+ }
325
+
326
+ get renderMode(): RenderMode {
327
+ return this.rendererSettings.renderMode;
328
+ }
329
+
330
+ set renderMode(renderMode: RenderMode) {
331
+ if (this.rendererSettings.renderMode !== renderMode) {
332
+ let needRestart = false;
333
+ if (this.rendererSettings.renderMode === RenderMode.Trail) {
334
+ needRestart = true;
335
+ }
336
+ if (this.rendererSettings.renderMode === RenderMode.Mesh) {
337
+ this.startRotation = new ConstantValue(0);
338
+ }
339
+ switch (renderMode) {
340
+ case RenderMode.Trail:
341
+ this.rendererEmitterSettings = {startLength: new ConstantValue(30), followLocalOrigin: false};
342
+ needRestart = true;
343
+ break;
344
+ case RenderMode.Mesh:
345
+ this.rendererEmitterSettings = {};
346
+ this.startRotation = new AxisAngleGenerator(new Vector3(0, 1, 0), new ConstantValue(0));
347
+ break;
348
+ case RenderMode.StretchedBillBoard:
349
+ this.rendererEmitterSettings = {speedFactor: 0, lengthFactor: 2};
350
+ break;
351
+ case RenderMode.BillBoard:
352
+ case RenderMode.VerticalBillBoard:
353
+ case RenderMode.HorizontalBillBoard:
354
+ this.rendererEmitterSettings = {};
355
+ break;
356
+ }
357
+ this.rendererSettings.renderMode = renderMode;
358
+ if (needRestart) {
359
+ this.restart();
360
+ this.particles.length = 0;
361
+ }
362
+ this.neededToUpdateRender = true;
363
+ }
364
+ }
365
+
366
+ get renderOrder(): number {
367
+ return this.rendererSettings.renderOrder;
368
+ }
369
+
370
+ set renderOrder(renderOrder: number) {
371
+ this.rendererSettings.renderOrder = renderOrder;
372
+ this.neededToUpdateRender = true;
373
+ }
374
+
375
+ constructor(parameters: ParticleSystemParameters) {
376
+ this.layerMaskProxy = {mask: parameters.layerMask ?? 0x0FFFFFFF};
377
+ Object.defineProperty(this.layerMaskProxy, 'mask', {
378
+ enumerable: true,
379
+ get: () => this.rendererSettings.layerMask,
380
+ set: (mask: number) => {
381
+ this.rendererSettings.layerMask = mask;
382
+ this.neededToUpdateRender = true;
383
+ },
384
+ });
385
+
386
+ this.autoDestroy = parameters.autoDestroy ?? false;
387
+ this.duration = parameters.duration ?? 1;
388
+ this.startDelay = parameters.startDelay ?? new ConstantValue(0);
389
+ this.looping = parameters.looping ?? true;
390
+ this.prewarm = parameters.prewarm ?? false;
391
+ this.startLife = parameters.startLife ?? new ConstantValue(5);
392
+ this.startSpeed = parameters.startSpeed ?? new ConstantValue(0);
393
+ this.startRotation = parameters.startRotation ?? new ConstantValue(0);
394
+ this.startSize = parameters.startSize ?? new ConstantValue(1);
395
+ this.startColor = parameters.startColor ?? new ConstantColor(new Vector4(1, 1, 1, 1));
396
+ this.emissionOverTime = parameters.emissionOverTime ?? new ConstantValue(10);
397
+ this.emissionOverDistance = parameters.emissionOverDistance ?? new ConstantValue(0);
398
+ this.emissionBursts = parameters.emissionBursts ?? [];
399
+ this.onlyUsedByOther = parameters.onlyUsedByOther ?? false;
400
+ this.emitterShape = parameters.shape ?? new SphereEmitter();
401
+ this.behaviors = parameters.behaviors ?? [];
402
+ this.worldSpace = parameters.worldSpace ?? false;
403
+ this.rendererEmitterSettings = parameters.rendererEmitterSettings ?? {};
404
+
405
+ if (parameters.renderMode === RenderMode.StretchedBillBoard) {
406
+ const settings = this.rendererEmitterSettings as StretchedBillBoardSettings;
407
+ if (parameters.speedFactor !== undefined) {
408
+ settings.speedFactor = parameters.speedFactor;
409
+ }
410
+ settings.speedFactor = settings.speedFactor ?? 0;
411
+ settings.lengthFactor = settings.lengthFactor ?? 0;
412
+ }
413
+
414
+ this.rendererSettings = {
415
+ instancingGeometry: parameters.instancingGeometry ?? DEFAULT_POSITIONS,
416
+ instancingIndices: parameters.instancingIndices ?? DEFAULT_INDICES,
417
+ instancingUVs: parameters.instancingUVs ?? DEFAULT_UVS,
418
+ instancingNormals: parameters.instancingNormals,
419
+ renderMode: parameters.renderMode ?? RenderMode.BillBoard,
420
+ renderOrder: parameters.renderOrder ?? 0,
421
+ uTileCount: parameters.uTileCount ?? 1,
422
+ vTileCount: parameters.vTileCount ?? 1,
423
+ blendTiles: parameters.blendTiles ?? false,
424
+ softParticles: parameters.softParticles ?? false,
425
+ softNearFade: parameters.softNearFade ?? 0,
426
+ softFarFade: parameters.softFarFade ?? 0,
427
+ materialBlendMode: parameters.blendMode ?? Constants.ALPHA_ADD,
428
+ materialTransparent: parameters.transparent ?? true,
429
+ materialDepthTest: parameters.depthTest ?? true,
430
+ materialDepthWrite: parameters.depthWrite ?? false,
431
+ materialAlphaTest: parameters.alphaTest ?? 0,
432
+ texture: parameters.texture ?? null,
433
+ layerMask: parameters.layerMask ?? 0x0FFFFFFF,
434
+ };
435
+ if (this.rendererSettings.renderMode === RenderMode.Mesh && !this.rendererSettings.instancingNormals) {
436
+ this.rendererSettings.instancingNormals = ParticleSystem.createFallbackNormals(this.rendererSettings.instancingGeometry);
437
+ }
438
+
439
+ this.materialRef = parameters.material ?? null;
440
+ this.applyMaterialSettings(this.materialRef, {
441
+ blendMode: parameters.blendMode,
442
+ transparent: parameters.transparent,
443
+ depthTest: parameters.depthTest,
444
+ depthWrite: parameters.depthWrite,
445
+ alphaTest: parameters.alphaTest,
446
+ texture: parameters.texture,
447
+ layerMask: parameters.layerMask,
448
+ });
449
+ this.neededToUpdateRender = true;
450
+
451
+ this.particles = [];
452
+ this.startTileIndex = parameters.startTileIndex || new ConstantValue(0);
453
+ this.emitter = new ParticleEmitter(this, parameters.scene);
454
+
455
+ this.paused = false;
456
+ this.particleNum = 0;
457
+ this.emissionState = {
458
+ isBursting: false,
459
+ burstParticleIndex: 0,
460
+ burstParticleCount: 0,
461
+ burstIndex: 0,
462
+ burstWaveIndex: 0,
463
+ time: 0,
464
+ waitEmiting: 0,
465
+ travelDistance: 0,
466
+ };
467
+
468
+ this.emissionBursts.forEach((burst) => burst.count.startGen(this.memory));
469
+ this.emissionOverDistance.startGen(this.memory);
470
+ this.startDelay.startGen(this.memory);
471
+ this.startDelayTimeLeft = this.startDelay.genValue(this.memory, 0);
472
+ this.refreshTrailHistoryMode();
473
+
474
+ this.emitEnded = false;
475
+ this.markForDestroy = false;
476
+ this.prewarmed = false;
477
+ }
478
+
479
+ private applyMaterialSettings(
480
+ material: any,
481
+ overrides: {
482
+ blendMode?: number;
483
+ transparent?: boolean;
484
+ depthTest?: boolean;
485
+ depthWrite?: boolean;
486
+ alphaTest?: number;
487
+ texture?: Texture | null;
488
+ layerMask?: number;
489
+ } = {}
490
+ ) {
491
+ /* istanbul ignore if -- rendererSettings is always assigned in the constructor before any code path can call this private method */
492
+ if (!this.rendererSettings) {
493
+ return;
494
+ }
495
+
496
+ const resolvedTexture =
497
+ overrides.texture !== undefined
498
+ ? overrides.texture
499
+ : (material?.albedoTexture ??
500
+ material?.diffuseTexture ??
501
+ material?.emissiveTexture ??
502
+ material?.opacityTexture ??
503
+ material?.baseTexture ??
504
+ this.rendererSettings.texture ??
505
+ null);
506
+ const resolvedBlendMode =
507
+ overrides.blendMode ??
508
+ (typeof material?.alphaMode === 'number' ? material.alphaMode : this.rendererSettings.materialBlendMode);
509
+ const resolvedTransparent =
510
+ overrides.transparent ??
511
+ (typeof material?.needAlphaBlending === 'function'
512
+ ? material.needAlphaBlending()
513
+ : typeof material?.alpha === 'number'
514
+ ? material.alpha < 1
515
+ : this.rendererSettings.materialTransparent);
516
+ const resolvedDepthTest =
517
+ overrides.depthTest ??
518
+ (typeof material?.disableDepthTest === 'boolean'
519
+ ? !material.disableDepthTest
520
+ : this.rendererSettings.materialDepthTest);
521
+ const resolvedDepthWrite =
522
+ overrides.depthWrite ??
523
+ (typeof material?.disableDepthWrite === 'boolean'
524
+ ? !material.disableDepthWrite
525
+ : typeof material?.forceDepthWrite === 'boolean'
526
+ ? material.forceDepthWrite
527
+ : this.rendererSettings.materialDepthWrite);
528
+ const resolvedAlphaTest =
529
+ overrides.alphaTest ??
530
+ (typeof material?.alphaCutOff === 'number'
531
+ ? material.alphaCutOff
532
+ : typeof material?.alphaCutOffValue === 'number'
533
+ ? material.alphaCutOffValue
534
+ : this.rendererSettings.materialAlphaTest);
535
+
536
+ this.rendererSettings.texture = resolvedTexture;
537
+ this.rendererSettings.materialBlendMode = resolvedBlendMode;
538
+ this.rendererSettings.materialTransparent = resolvedTransparent;
539
+ this.rendererSettings.materialDepthTest = resolvedDepthTest;
540
+ this.rendererSettings.materialDepthWrite = resolvedDepthWrite;
541
+ this.rendererSettings.materialAlphaTest = resolvedAlphaTest;
542
+ if (overrides.layerMask !== undefined) {
543
+ this.rendererSettings.layerMask = overrides.layerMask;
544
+ }
545
+ this.neededToUpdateRender = true;
546
+ }
547
+
548
+ pause() { this.paused = true; }
549
+ play() { this.paused = false; }
550
+ stop() { this.restart(); this.pause(); }
551
+
552
+ private refreshTrailHistoryMode() {
553
+ this.useFastTrailHistory = !this.behaviors.some((behavior) => (behavior as any).type === 'WidthOverLength');
554
+ }
555
+
556
+ private ensureTrailHistoryCapacity(particle: TrailParticle, capacity: number) {
557
+ const trailAny = particle as any;
558
+ if (trailAny._trailHistoryCapacity === capacity && trailAny._trailHistoryPositions) {
559
+ return;
560
+ }
561
+ trailAny._trailHistoryCapacity = capacity;
562
+ trailAny._trailHistoryPositions = new Float32Array(capacity * 3);
563
+ trailAny._trailHistorySizes = new Float32Array(capacity);
564
+ trailAny._trailHistoryColors = new Float32Array(capacity * 4);
565
+ trailAny._trailHistoryHead = 0;
566
+ trailAny._trailHistoryCount = 0;
567
+ }
568
+
569
+ private resetTrailHistory(particle: TrailParticle) {
570
+ const trailAny = particle as any;
571
+ trailAny._trailHistoryHead = 0;
572
+ trailAny._trailHistoryCount = 0;
573
+ }
574
+
575
+ private getTrailHistoryCount(particle: TrailParticle): number {
576
+ const trailAny = particle as any;
577
+ const fastHistoryCount = trailAny._trailHistoryCount;
578
+ if (typeof fastHistoryCount === 'number') {
579
+ return fastHistoryCount;
580
+ }
581
+ return particle.previous.length;
582
+ }
583
+
584
+ private updateFastTrailHistory(particle: TrailParticle) {
585
+ const trailAny = particle as any;
586
+ const capacity = Math.max(1, Math.ceil(particle.length));
587
+ this.ensureTrailHistoryCapacity(particle, capacity);
588
+ let head = trailAny._trailHistoryHead as number;
589
+ let count = trailAny._trailHistoryCount as number;
590
+ const positions = trailAny._trailHistoryPositions as Float32Array;
591
+ const sizes = trailAny._trailHistorySizes as Float32Array;
592
+ const colors = trailAny._trailHistoryColors as Float32Array;
593
+
594
+ if (particle.age <= particle.life) {
595
+ const posIndex = head * 3;
596
+ positions[posIndex] = particle.position.x;
597
+ positions[posIndex + 1] = particle.position.y;
598
+ positions[posIndex + 2] = particle.position.z;
599
+ sizes[head] = particle.size.x;
600
+ const colorIndex = head * 4;
601
+ colors[colorIndex] = particle.color.x;
602
+ colors[colorIndex + 1] = particle.color.y;
603
+ colors[colorIndex + 2] = particle.color.z;
604
+ colors[colorIndex + 3] = particle.color.w;
605
+ head = (head + 1) % capacity;
606
+ if (count < capacity) {
607
+ count++;
608
+ }
609
+ } else if (count > 0) {
610
+ count--;
611
+ }
612
+
613
+ trailAny._trailHistoryHead = head;
614
+ trailAny._trailHistoryCount = count;
615
+ }
616
+
617
+ setQualityFactor(qualityFactor: number) {
618
+ this.qualityFactor = Math.max(0.1, Math.min(1, qualityFactor));
619
+ }
620
+
621
+ private spawn(count: number, emissionState: EmissionState, matrix: Matrix4) {
622
+ tempQ.setFromRotationMatrix(matrix);
623
+ const translation = tempV;
624
+ const scale = tempV2;
625
+ matrix.decompose(translation, tempQ, scale);
626
+ const behaviors = this.behaviors;
627
+ const behaviorCount = behaviors.length;
628
+ const renderMode = this.rendererSettings.renderMode;
629
+ const isTrailMode = renderMode === RenderMode.Trail;
630
+ const timeRatio = emissionState.time / this.duration;
631
+ const trailSettings = isTrailMode ? (this.rendererEmitterSettings as TrailSettings) : undefined;
632
+ const followLocalOrigin = Boolean(trailSettings?.followLocalOrigin);
633
+
634
+ for (let i = 0; i < count; i++) {
635
+ emissionState.burstParticleIndex = i;
636
+ this.particleNum++;
637
+ if (this.particles.length < this.particleNum) {
638
+ if (isTrailMode) {
639
+ this.particles.push(new TrailParticle());
640
+ } else {
641
+ this.particles.push(new SpriteParticle());
642
+ }
643
+ }
644
+ const particle = this.particles[this.particleNum - 1];
645
+ particle.reset();
646
+ particle.speedModifier = 1;
647
+ this.startColor.startGen(particle.memory);
648
+ this.startColor.genColor(particle.memory, particle.startColor, this.emissionState.time);
649
+ particle.color.copy(particle.startColor);
650
+ this.startSpeed.startGen(particle.memory);
651
+ particle.startSpeed = this.startSpeed.genValue(particle.memory, timeRatio);
652
+ this.startLife.startGen(particle.memory);
653
+ particle.life = this.startLife.genValue(particle.memory, timeRatio);
654
+ particle.age = 0;
655
+ this.startSize.startGen(particle.memory);
656
+ if (this.startSize.type === 'vec3function') {
657
+ (this.startSize as Vector3Generator).genValue(particle.memory, particle.startSize, timeRatio);
658
+ } else {
659
+ const size = (this.startSize as FunctionValueGenerator).genValue(particle.memory, timeRatio);
660
+ particle.startSize.set(size, size, size);
661
+ }
662
+ this.startTileIndex.startGen(particle.memory);
663
+ particle.uvTile = this.startTileIndex.genValue(particle.memory);
664
+ particle.size.copy(particle.startSize);
665
+
666
+ if (
667
+ this.rendererSettings.renderMode === RenderMode.Mesh ||
668
+ this.rendererSettings.renderMode === RenderMode.BillBoard ||
669
+ this.rendererSettings.renderMode === RenderMode.VerticalBillBoard ||
670
+ this.rendererSettings.renderMode === RenderMode.HorizontalBillBoard ||
671
+ this.rendererSettings.renderMode === RenderMode.StretchedBillBoard
672
+ ) {
673
+ const sprite = particle as SpriteParticle;
674
+ this.startRotation.startGen(particle.memory);
675
+ if (renderMode === RenderMode.Mesh) {
676
+ if (!(sprite.rotation instanceof Quaternion)) {
677
+ sprite.rotation = new Quaternion();
678
+ }
679
+ if (this.startRotation.type === 'rotation') {
680
+ this.startRotation.genValue(particle.memory, sprite.rotation as Quaternion, 1, timeRatio);
681
+ } else {
682
+ (sprite.rotation as Quaternion).setFromAxisAngle(UP, this.startRotation.genValue(sprite.memory, timeRatio));
683
+ }
684
+ } else {
685
+ if (this.startRotation.type === 'rotation') {
686
+ sprite.rotation = 0;
687
+ } else {
688
+ sprite.rotation = this.startRotation.genValue(sprite.memory, timeRatio);
689
+ }
690
+ }
691
+ } else if (isTrailMode && trailSettings) {
692
+ const trail = particle as TrailParticle;
693
+ trailSettings.startLength.startGen(trail.memory);
694
+ trail.length = trailSettings.startLength.genValue(trail.memory, timeRatio);
695
+ if (this.useFastTrailHistory) {
696
+ this.ensureTrailHistoryCapacity(trail, Math.max(1, Math.ceil(trail.length)));
697
+ this.resetTrailHistory(trail);
698
+ }
699
+ }
700
+
701
+ this.emitterShape.initialize(particle, emissionState);
702
+
703
+ if (isTrailMode && followLocalOrigin) {
704
+ const trail = particle as TrailParticle;
705
+ if (!trail.localPosition) {
706
+ trail.localPosition = new Vector3();
707
+ }
708
+ trail.localPosition.copy(trail.position);
709
+ }
710
+
711
+ if (this.worldSpace) {
712
+ particle.position.applyMatrix4(matrix);
713
+ particle.startSize.multiply(scale).abs();
714
+ particle.size.copy(particle.startSize);
715
+ particle.velocity.multiply(scale).applyMatrix3(this.normalMatrix);
716
+ if (particle.rotation && particle.rotation instanceof Quaternion) {
717
+ particle.rotation.multiplyQuaternions(tempQ, particle.rotation);
718
+ }
719
+ } else {
720
+ if (this.onlyUsedByOther) {
721
+ particle.parentMatrix = matrix;
722
+ }
723
+ }
724
+
725
+ for (let j = 0; j < behaviorCount; j++) {
726
+ behaviors[j].initialize(particle, this);
727
+ }
728
+ }
729
+ }
730
+
731
+ endEmit() {
732
+ this.emitEnded = true;
733
+ if (this.autoDestroy) {
734
+ this.markForDestroy = true;
735
+ }
736
+ this.fire({type: 'emitEnd', particleSystem: this});
737
+ }
738
+
739
+ dispose() {
740
+ if (this._renderer) this._renderer.deleteSystem(this);
741
+ this.emitter.dispose();
742
+ this.fire({type: 'destroy', particleSystem: this});
743
+ }
744
+
745
+ restart() {
746
+ this.memory.length = 0;
747
+ this.paused = false;
748
+ this.particleNum = 0;
749
+ this.emissionState.isBursting = false;
750
+ this.emissionState.burstIndex = 0;
751
+ this.emissionState.burstWaveIndex = 0;
752
+ this.emissionState.time = 0;
753
+ this.emissionState.waitEmiting = 0;
754
+ this.behaviors.forEach((behavior) => behavior.reset());
755
+ this.emitEnded = false;
756
+ this.markForDestroy = false;
757
+ this.prewarmed = false;
758
+ this.emissionBursts.forEach((burst) => burst.count.startGen(this.memory));
759
+ this.emissionOverDistance.startGen(this.memory);
760
+ this.startDelay.startGen(this.memory);
761
+ this.startDelayTimeLeft = this.startDelay.genValue(this.memory, 0);
762
+ this.previousEmitterPos = undefined;
763
+ this.emitterVelocity.set(0, 0, 0);
764
+ }
765
+
766
+ private firstTimeUpdate = true;
767
+
768
+ /** @internal */
769
+ update(delta: number) {
770
+ if (this.paused) return;
771
+
772
+ if (this.firstTimeUpdate) {
773
+ this.firstTimeUpdate = false;
774
+ this.emitter.computeWorldMatrix(true);
775
+ }
776
+
777
+ if (this.emitEnded && this.particleNum === 0) {
778
+ if (this.markForDestroy) this.dispose();
779
+ return;
780
+ }
781
+
782
+ if (this.looping && this.prewarm && !this.prewarmed) {
783
+ this.prewarmed = true;
784
+ for (let i = 0; i < this.duration * PREWARM_FPS; i++) {
785
+ this.update(1.0 / PREWARM_FPS);
786
+ }
787
+ }
788
+
789
+ if (delta > 0.1) {
790
+ delta = 0.1;
791
+ }
792
+
793
+ if (this.neededToUpdateRender) {
794
+ if (this._renderer) this._renderer.updateSystem(this);
795
+ this.neededToUpdateRender = false;
796
+ }
797
+
798
+ const emitterElements = this.emitter.matrixWorld.elements;
799
+ this.tempEmitterPos.set(emitterElements[12], emitterElements[13], emitterElements[14]);
800
+ if (this.previousEmitterPos !== undefined && delta > 0) {
801
+ this.emitterVelocity
802
+ .copy(this.tempEmitterPos)
803
+ .sub(this.previousEmitterPos)
804
+ .divideScalar(delta);
805
+ }
806
+ (this.previousEmitterPos ??= new Vector3()).copy(this.tempEmitterPos);
807
+
808
+ // Start delay holds back emission once per play/restart (ignored with
809
+ // prewarm, matching Unity). Carry the remainder so a large frame does
810
+ // not lose time.
811
+ if (this.startDelayTimeLeft > 0 && !(this.prewarm && this.looping)) {
812
+ this.startDelayTimeLeft -= delta;
813
+ if (this.startDelayTimeLeft >= 0) return;
814
+ delta = Math.min(-this.startDelayTimeLeft, 0.1);
815
+ this.startDelayTimeLeft = 0;
816
+ }
817
+
818
+ if (!this.onlyUsedByOther) {
819
+ this.emit(delta, this.emissionState, this.emitter.matrixWorld);
820
+ }
821
+
822
+ const particles = this.particles;
823
+ const behaviorCount = this.behaviors.length;
824
+ const particleCount = this.particleNum;
825
+ this.emitterShape.update(this, delta);
826
+ for (let j = 0; j < behaviorCount; j++) {
827
+ const behavior = this.behaviors[j];
828
+ behavior.frameUpdate(delta);
829
+ for (let i = 0; i < particleCount; i++) {
830
+ const particle = particles[i];
831
+ if (!particle.died) {
832
+ behavior.update(particle, delta);
833
+ }
834
+ }
835
+ }
836
+
837
+ const followLocalOrigin =
838
+ this.rendererSettings.renderMode === RenderMode.Trail &&
839
+ (this.rendererEmitterSettings as TrailSettings).followLocalOrigin;
840
+ const emitterMatrix = this.emitter.matrixWorld;
841
+ for (let i = 0; i < particleCount; i++) {
842
+ const particle = particles[i];
843
+ if (followLocalOrigin && (particle as TrailParticle).localPosition) {
844
+ particle.position.copy((particle as TrailParticle).localPosition!);
845
+ if (particle.parentMatrix) {
846
+ particle.position.applyMatrix4(particle.parentMatrix);
847
+ } else {
848
+ particle.position.applyMatrix4(emitterMatrix);
849
+ }
850
+ } else {
851
+ particle.position.addScaledVector(particle.velocity, delta * particle.speedModifier);
852
+ }
853
+ particle.age += delta;
854
+ }
855
+
856
+ if (this.rendererSettings.renderMode === RenderMode.Trail) {
857
+ for (let i = 0; i < particleCount; i++) {
858
+ const trailParticle = particles[i] as TrailParticle;
859
+ if (this.useFastTrailHistory) {
860
+ this.updateFastTrailHistory(trailParticle);
861
+ } else {
862
+ trailParticle.update();
863
+ }
864
+ }
865
+ }
866
+
867
+ let liveParticleCount = this.particleNum;
868
+ for (let i = 0; i < liveParticleCount; i++) {
869
+ const particle = particles[i];
870
+ if (
871
+ particle.died &&
872
+ (!(particle instanceof TrailParticle) || this.getTrailHistoryCount(particle as TrailParticle) === 0)
873
+ ) {
874
+ particles[i] = particles[liveParticleCount - 1];
875
+ particles[liveParticleCount - 1] = particle;
876
+ liveParticleCount--;
877
+ i--;
878
+ this.fire({type: 'particleDied', particleSystem: this, particle: particle});
879
+ }
880
+ }
881
+ this.particleNum = liveParticleCount;
882
+ }
883
+
884
+ emit(delta: number, emissionState: EmissionState, emitterMatrix: Matrix4) {
885
+ if (emissionState.time > this.duration) {
886
+ if (this.looping) {
887
+ emissionState.time -= this.duration;
888
+ emissionState.burstIndex = 0;
889
+ this.behaviors.forEach((behavior) => behavior.reset());
890
+ } else {
891
+ if (!this.emitEnded && !this.onlyUsedByOther) {
892
+ this.endEmit();
893
+ }
894
+ }
895
+ }
896
+
897
+ this.normalMatrix.getNormalMatrix(emitterMatrix);
898
+ const emissionBursts = this.emissionBursts;
899
+ const emissionBurstCount = emissionBursts.length;
900
+ const qualityFactor = this.qualityFactor;
901
+
902
+ const totalSpawn = Math.ceil(emissionState.waitEmiting);
903
+ this.spawn(totalSpawn, emissionState, emitterMatrix);
904
+ emissionState.waitEmiting -= totalSpawn;
905
+
906
+ while (
907
+ emissionState.burstIndex < emissionBurstCount &&
908
+ emissionBursts[emissionState.burstIndex].time <= emissionState.time
909
+ ) {
910
+ const burst = emissionBursts[emissionState.burstIndex];
911
+ if (Math.random() < burst.probability) {
912
+ const rawCount = burst.count.genValue(this.memory, this.time);
913
+ const count = qualityFactor >= 0.999 ? rawCount : Math.floor(rawCount * qualityFactor);
914
+ emissionState.isBursting = true;
915
+ emissionState.burstParticleCount = count;
916
+ this.spawn(count, emissionState, emitterMatrix);
917
+ emissionState.isBursting = false;
918
+ }
919
+ emissionState.burstIndex++;
920
+ }
921
+
922
+ if (!this.emitEnded) {
923
+ const timeRatio = emissionState.time / this.duration;
924
+ emissionState.waitEmiting += delta * this.emissionOverTime.genValue(this.memory, timeRatio) * qualityFactor;
925
+
926
+ const previousWorldPos = emissionState.previousWorldPos;
927
+ if (previousWorldPos !== undefined) {
928
+ this.temp.set(emitterMatrix.elements[12], emitterMatrix.elements[13], emitterMatrix.elements[14]);
929
+ emissionState.travelDistance += previousWorldPos.distanceTo(this.temp);
930
+ const emitPerMeter = this.emissionOverDistance.genValue(this.memory, timeRatio) * qualityFactor;
931
+ if (emissionState.travelDistance * emitPerMeter > 0) {
932
+ const count = Math.floor(emissionState.travelDistance * emitPerMeter);
933
+ emissionState.travelDistance -= count / emitPerMeter;
934
+ emissionState.waitEmiting += count;
935
+ }
936
+ }
937
+ }
938
+ if (emissionState.previousWorldPos === undefined) emissionState.previousWorldPos = new Vector3();
939
+ emissionState.previousWorldPos.set(emitterMatrix.elements[12], emitterMatrix.elements[13], emitterMatrix.elements[14]);
940
+ emissionState.time += delta;
941
+ }
942
+
943
+ toJSON(metaData?: BabylonMetaData | string, _options: SerializationOptions = {}): ParticleSystemJSONParameters {
944
+ const isRootObject = metaData === undefined || typeof metaData === 'string';
945
+ const meta: BabylonMetaData = isRootObject
946
+ ? {
947
+ geometries: {},
948
+ materials: {},
949
+ textures: {},
950
+ images: {},
951
+ shapes: {},
952
+ skeletons: {},
953
+ animations: {},
954
+ nodes: {},
955
+ }
956
+ : metaData;
957
+
958
+ const geometryUUID = this.ensureGeometryMeta(meta);
959
+ const materialUUID = this.ensureMaterialMeta(meta);
960
+ const rendererEmitterSettingsJSON = this.toRendererSettingsJSON();
961
+
962
+ return {
963
+ version: '3.0',
964
+ autoDestroy: this.autoDestroy,
965
+ looping: this.looping,
966
+ prewarm: this.prewarm,
967
+ duration: this.duration,
968
+ startDelay: this.startDelay.toJSON(),
969
+ shape: this.emitterShape.toJSON(),
970
+ startLife: this.startLife.toJSON(),
971
+ startSpeed: this.startSpeed.toJSON(),
972
+ startRotation: this.startRotation.toJSON(),
973
+ startSize: this.startSize.toJSON(),
974
+ startColor: this.startColor.toJSON(),
975
+ emissionOverTime: this.emissionOverTime.toJSON(),
976
+ emissionOverDistance: this.emissionOverDistance.toJSON(),
977
+ emissionBursts: this.emissionBursts.map((burst) => ({
978
+ time: burst.time,
979
+ count: burst.count.toJSON(),
980
+ probability: burst.probability,
981
+ interval: burst.interval,
982
+ cycle: burst.cycle,
983
+ })),
984
+ onlyUsedByOther: this.onlyUsedByOther,
985
+ instancingGeometry: geometryUUID,
986
+ renderMode: this.renderMode,
987
+ renderOrder: this.renderOrder,
988
+ rendererEmitterSettings: rendererEmitterSettingsJSON,
989
+ material: materialUUID,
990
+ layers: this.rendererSettings.layerMask,
991
+ startTileIndex: this.startTileIndex.toJSON(),
992
+ uTileCount: this.rendererSettings.uTileCount,
993
+ vTileCount: this.rendererSettings.vTileCount,
994
+ blendTiles: this.rendererSettings.blendTiles,
995
+ softParticles: this.rendererSettings.softParticles,
996
+ softFarFade: this.rendererSettings.softFarFade,
997
+ softNearFade: this.rendererSettings.softNearFade,
998
+ blending: this.rendererSettings.materialBlendMode,
999
+ transparent: this.rendererSettings.materialTransparent,
1000
+ depthTest: this.rendererSettings.materialDepthTest,
1001
+ depthWrite: this.rendererSettings.materialDepthWrite,
1002
+ alphaTest: this.rendererSettings.materialAlphaTest,
1003
+ behaviors: this.behaviors.map((b) => b.toJSON()),
1004
+ worldSpace: this.worldSpace,
1005
+ };
1006
+ }
1007
+
1008
+ static fromJSON(
1009
+ json: ParticleSystemJSONParameters,
1010
+ meta: BabylonMetaData,
1011
+ dependencies: {[uuid: string]: Behavior} = {},
1012
+ scene?: Scene
1013
+ ): ParticleSystem {
1014
+ const shape = EmitterFromJSON(json.shape, meta as any);
1015
+ let rendererEmitterSettings: RendererEmitterSettings;
1016
+ if (json.renderMode === RenderMode.Trail) {
1017
+ const trailSettings = json.rendererEmitterSettings as TrailSettings;
1018
+ rendererEmitterSettings = {
1019
+ startLength:
1020
+ trailSettings?.startLength !== undefined
1021
+ ? ValueGeneratorFromJSON(trailSettings.startLength)
1022
+ : new ConstantValue(30),
1023
+ followLocalOrigin: trailSettings?.followLocalOrigin ?? false,
1024
+ };
1025
+ } else if (json.renderMode === RenderMode.StretchedBillBoard) {
1026
+ rendererEmitterSettings = {...(json.rendererEmitterSettings ?? {})};
1027
+ if (json.speedFactor !== undefined) {
1028
+ (rendererEmitterSettings as StretchedBillBoardSettings).speedFactor = json.speedFactor;
1029
+ }
1030
+ } else {
1031
+ rendererEmitterSettings = {};
1032
+ }
1033
+
1034
+ const materialMeta = json.material ? meta.materials?.[json.material] : undefined;
1035
+ const materialTextureRef = materialMeta?.texture ?? json.texture;
1036
+ const texture =
1037
+ typeof materialTextureRef === 'string'
1038
+ ? meta.textures?.[materialTextureRef] ?? null
1039
+ : materialTextureRef ?? null;
1040
+ const resolvedGeometryEntry =
1041
+ typeof json.instancingGeometry === 'string'
1042
+ ? meta.geometries?.[json.instancingGeometry]
1043
+ : json.instancingGeometry;
1044
+ const resolvedGeometry = ParticleSystem.resolveGeometryData(resolvedGeometryEntry);
1045
+
1046
+ const ps = new ParticleSystem({
1047
+ scene,
1048
+ autoDestroy: json.autoDestroy,
1049
+ looping: json.looping,
1050
+ prewarm: json.prewarm,
1051
+ duration: json.duration,
1052
+ startDelay: json.startDelay ? ValueGeneratorFromJSON(json.startDelay) : undefined,
1053
+ shape,
1054
+ startLife: ValueGeneratorFromJSON(json.startLife),
1055
+ startSpeed: ValueGeneratorFromJSON(json.startSpeed),
1056
+ startRotation: GeneratorFromJSON(json.startRotation) as RotationGenerator | ValueGenerator | FunctionValueGenerator,
1057
+ startSize: GeneratorFromJSON(json.startSize) as Vector3Generator | ValueGenerator | FunctionValueGenerator,
1058
+ startColor: ColorGeneratorFromJSON(json.startColor) as ColorGenerator,
1059
+ emissionOverTime: ValueGeneratorFromJSON(json.emissionOverTime),
1060
+ emissionOverDistance: ValueGeneratorFromJSON(json.emissionOverDistance),
1061
+ emissionBursts: json.emissionBursts?.map((burst: any) => ({
1062
+ time: burst.time,
1063
+ count: typeof burst.count === 'number' ? new ConstantValue(burst.count) : ValueGeneratorFromJSON(burst.count),
1064
+ probability: burst.probability ?? 1,
1065
+ interval: burst.interval ?? 0.1,
1066
+ cycle: burst.cycle ?? burst.cycleCount ?? 1,
1067
+ })),
1068
+ onlyUsedByOther: json.onlyUsedByOther,
1069
+ instancingGeometry: resolvedGeometry.positions,
1070
+ instancingIndices: resolvedGeometry.indices,
1071
+ instancingUVs: resolvedGeometry.uvs,
1072
+ instancingNormals: resolvedGeometry.normals,
1073
+ renderMode: json.renderMode,
1074
+ rendererEmitterSettings,
1075
+ renderOrder: json.renderOrder,
1076
+ texture,
1077
+ material: materialMeta?.sourceMaterial,
1078
+ blendMode: materialMeta?.alphaMode ?? json.blending ?? Constants.ALPHA_ADD,
1079
+ transparent: materialMeta?.transparent ?? json.transparent ?? true,
1080
+ depthTest: materialMeta?.depthTest ?? json.depthTest ?? true,
1081
+ depthWrite: materialMeta?.depthWrite ?? json.depthWrite ?? false,
1082
+ alphaTest: materialMeta?.alphaTest ?? json.alphaTest ?? 0,
1083
+ startTileIndex:
1084
+ typeof json.startTileIndex === 'number'
1085
+ ? new ConstantValue(json.startTileIndex)
1086
+ : (ValueGeneratorFromJSON(json.startTileIndex) as ValueGenerator),
1087
+ uTileCount: json.uTileCount,
1088
+ vTileCount: json.vTileCount,
1089
+ blendTiles: json.blendTiles,
1090
+ softParticles: json.softParticles,
1091
+ softFarFade: json.softFarFade,
1092
+ softNearFade: json.softNearFade,
1093
+ behaviors: [],
1094
+ worldSpace: json.worldSpace,
1095
+ layerMask: json.layers,
1096
+ });
1097
+ ps.behaviors = (json.behaviors ?? [])
1098
+ .map((behaviorJson) => {
1099
+ const behavior = BehaviorFromJSON(behaviorJson, ps);
1100
+ if (behavior && behavior.type === 'EmitSubParticleSystem') {
1101
+ dependencies[(behaviorJson as any).subParticleSystem] = behavior;
1102
+ }
1103
+ return behavior;
1104
+ })
1105
+ .filter((behavior) => behavior !== null) as Behavior[];
1106
+ return ps;
1107
+ }
1108
+
1109
+ private static nextSerializationId(prefix: string): string {
1110
+ ParticleSystem.serializationCounter += 1;
1111
+ return `${prefix}_${ParticleSystem.serializationCounter}`;
1112
+ }
1113
+
1114
+ private static createFallbackNormals(positions: Float32Array): Float32Array {
1115
+ const normals = new Float32Array(positions.length);
1116
+ for (let i = 0; i < normals.length; i += 3) {
1117
+ normals[i] = 0;
1118
+ normals[i + 1] = 0;
1119
+ normals[i + 2] = 1;
1120
+ }
1121
+ return normals;
1122
+ }
1123
+
1124
+ private toRendererSettingsJSON(): RendererEmitterSettings {
1125
+ if (this.renderMode === RenderMode.Trail) {
1126
+ return {
1127
+ startLength: (this.rendererEmitterSettings as TrailSettings).startLength.toJSON(),
1128
+ followLocalOrigin: (this.rendererEmitterSettings as TrailSettings).followLocalOrigin,
1129
+ };
1130
+ }
1131
+ if (this.renderMode === RenderMode.StretchedBillBoard) {
1132
+ return {
1133
+ speedFactor: (this.rendererEmitterSettings as StretchedBillBoardSettings).speedFactor,
1134
+ lengthFactor: (this.rendererEmitterSettings as StretchedBillBoardSettings).lengthFactor,
1135
+ };
1136
+ }
1137
+ return {};
1138
+ }
1139
+
1140
+ private ensureGeometryMeta(meta: BabylonMetaData): string {
1141
+ const geometryUUID = ParticleSystem.nextSerializationId('quarks_geometry');
1142
+ meta.geometries[geometryUUID] = {
1143
+ uuid: geometryUUID,
1144
+ type: 'QuarksGeometry',
1145
+ positions: Array.from(this.rendererSettings.instancingGeometry),
1146
+ indices: Array.from(this.rendererSettings.instancingIndices),
1147
+ uvs: this.rendererSettings.instancingUVs ? Array.from(this.rendererSettings.instancingUVs) : undefined,
1148
+ normals: this.rendererSettings.instancingNormals ? Array.from(this.rendererSettings.instancingNormals) : undefined,
1149
+ };
1150
+ return geometryUUID;
1151
+ }
1152
+
1153
+ private ensureMaterialMeta(meta: BabylonMetaData): string {
1154
+ const texture = this.rendererSettings.texture;
1155
+ let textureUUID: string | undefined;
1156
+ if (texture) {
1157
+ textureUUID = ParticleSystem.nextSerializationId('quarks_texture');
1158
+ meta.textures[textureUUID] = texture;
1159
+ }
1160
+
1161
+ const materialUUID = ParticleSystem.nextSerializationId('quarks_material');
1162
+ meta.materials[materialUUID] = {
1163
+ uuid: materialUUID,
1164
+ type: 'QuarksMaterial',
1165
+ transparent: this.rendererSettings.materialTransparent,
1166
+ alphaMode: this.rendererSettings.materialBlendMode,
1167
+ depthTest: this.rendererSettings.materialDepthTest,
1168
+ depthWrite: this.rendererSettings.materialDepthWrite,
1169
+ alphaTest: this.rendererSettings.materialAlphaTest,
1170
+ texture: textureUUID,
1171
+ sourceMaterial: this.materialRef ?? undefined,
1172
+ };
1173
+ return materialUUID;
1174
+ }
1175
+
1176
+ private static resolveGeometryData(entry: any): {
1177
+ positions: Float32Array;
1178
+ indices: Uint32Array | Uint16Array;
1179
+ uvs?: Float32Array;
1180
+ normals?: Float32Array;
1181
+ } {
1182
+ if (entry?.positions) {
1183
+ const positions =
1184
+ entry.positions instanceof Float32Array ? entry.positions : new Float32Array(entry.positions);
1185
+ const rawIndices =
1186
+ entry.indices instanceof Uint16Array || entry.indices instanceof Uint32Array
1187
+ ? entry.indices
1188
+ : entry.indices
1189
+ ? new Uint32Array(entry.indices)
1190
+ : undefined;
1191
+ return {
1192
+ positions,
1193
+ indices: ensureTriangleIndices(positions, rawIndices),
1194
+ uvs: entry.uvs
1195
+ ? entry.uvs instanceof Float32Array
1196
+ ? entry.uvs
1197
+ : new Float32Array(entry.uvs)
1198
+ : undefined,
1199
+ normals: entry.normals
1200
+ ? entry.normals instanceof Float32Array
1201
+ ? entry.normals
1202
+ : new Float32Array(entry.normals)
1203
+ : undefined,
1204
+ };
1205
+ }
1206
+
1207
+ const data = entry?.data ?? entry;
1208
+ if (data?.attributes?.position?.array) {
1209
+ const positions = new Float32Array(data.attributes.position.array);
1210
+ let indices: Uint32Array | Uint16Array | undefined;
1211
+ if (data.index?.array) {
1212
+ const indexType = data.index.type === 'Uint16Array' ? Uint16Array : Uint32Array;
1213
+ indices = new indexType(data.index.array);
1214
+ }
1215
+ return {
1216
+ positions,
1217
+ indices: ensureTriangleIndices(positions, indices),
1218
+ uvs: data.attributes.uv?.array ? new Float32Array(data.attributes.uv.array) : undefined,
1219
+ normals: data.attributes.normal?.array ? new Float32Array(data.attributes.normal.array) : undefined,
1220
+ };
1221
+ }
1222
+
1223
+ return {
1224
+ positions: DEFAULT_POSITIONS,
1225
+ indices: DEFAULT_INDICES,
1226
+ uvs: DEFAULT_UVS,
1227
+ };
1228
+ }
1229
+
1230
+ addBehavior(behavior: Behavior) {
1231
+ this.behaviors.push(behavior);
1232
+ this.refreshTrailHistoryMode();
1233
+ }
1234
+
1235
+ getRendererSettings(): VFXBatchSettings {
1236
+ return this.rendererSettings;
1237
+ }
1238
+
1239
+ addEventListener(event: ParticleSystemEventType, callback: (event: ParticleSystemEvent) => void): void {
1240
+ if (!this.listeners[event]) this.listeners[event] = [];
1241
+ this.listeners[event].push(callback);
1242
+ }
1243
+
1244
+ removeAllEventListeners(event: ParticleSystemEventType): void {
1245
+ if (this.listeners[event]) this.listeners[event] = [];
1246
+ }
1247
+
1248
+ removeEventListener(event: ParticleSystemEventType, callback: (event: ParticleSystemEvent) => void): void {
1249
+ if (this.listeners[event]) {
1250
+ const index = this.listeners[event].indexOf(callback);
1251
+ if (index !== -1) this.listeners[event].splice(index, 1);
1252
+ }
1253
+ }
1254
+
1255
+ private fire(event: ParticleSystemEvent) {
1256
+ if (this.listeners[event.type]) {
1257
+ this.listeners[event.type].forEach((callback) => callback(event));
1258
+ }
1259
+ }
1260
+
1261
+ clone(): ParticleSystem {
1262
+ const newBehaviors: Array<Behavior> = this.behaviors.map((b) => b.clone());
1263
+ let rendererEmitterSettings: RendererEmitterSettings;
1264
+
1265
+ if (this.renderMode === RenderMode.Trail) {
1266
+ rendererEmitterSettings = {
1267
+ startLength: (this.rendererEmitterSettings as TrailSettings).startLength.clone(),
1268
+ followLocalOrigin: (this.rendererEmitterSettings as TrailSettings).followLocalOrigin,
1269
+ };
1270
+ } else if (this.renderMode === RenderMode.StretchedBillBoard) {
1271
+ rendererEmitterSettings = {
1272
+ lengthFactor: (this.rendererEmitterSettings as StretchedBillBoardSettings).lengthFactor,
1273
+ speedFactor: (this.rendererEmitterSettings as StretchedBillBoardSettings).speedFactor,
1274
+ };
1275
+ } else {
1276
+ rendererEmitterSettings = {};
1277
+ }
1278
+
1279
+ return new ParticleSystem({
1280
+ autoDestroy: this.autoDestroy,
1281
+ looping: this.looping,
1282
+ prewarm: this.prewarm,
1283
+ duration: this.duration,
1284
+ startDelay: this.startDelay.clone(),
1285
+ shape: this.emitterShape.clone(),
1286
+ startLife: this.startLife.clone(),
1287
+ startSpeed: this.startSpeed.clone(),
1288
+ startRotation: this.startRotation.clone(),
1289
+ startSize: this.startSize.clone(),
1290
+ startColor: this.startColor.clone(),
1291
+ emissionOverTime: this.emissionOverTime.clone(),
1292
+ emissionOverDistance: this.emissionOverDistance.clone(),
1293
+ emissionBursts: this.emissionBursts.map((b) => ({...b})),
1294
+ onlyUsedByOther: this.onlyUsedByOther,
1295
+ instancingGeometry: this.rendererSettings.instancingGeometry,
1296
+ instancingIndices: this.rendererSettings.instancingIndices,
1297
+ instancingUVs: this.rendererSettings.instancingUVs,
1298
+ instancingNormals: this.rendererSettings.instancingNormals,
1299
+ renderMode: this.renderMode,
1300
+ renderOrder: this.renderOrder,
1301
+ rendererEmitterSettings,
1302
+ material: this.materialRef,
1303
+ texture: this.rendererSettings.texture,
1304
+ startTileIndex: this.startTileIndex,
1305
+ uTileCount: this.rendererSettings.uTileCount,
1306
+ vTileCount: this.rendererSettings.vTileCount,
1307
+ blendTiles: this.rendererSettings.blendTiles,
1308
+ softParticles: this.rendererSettings.softParticles,
1309
+ softFarFade: this.rendererSettings.softFarFade,
1310
+ softNearFade: this.rendererSettings.softNearFade,
1311
+ behaviors: newBehaviors,
1312
+ worldSpace: this.worldSpace,
1313
+ blendMode: this.rendererSettings.materialBlendMode,
1314
+ transparent: this.rendererSettings.materialTransparent,
1315
+ depthTest: this.rendererSettings.materialDepthTest,
1316
+ depthWrite: this.rendererSettings.materialDepthWrite,
1317
+ alphaTest: this.rendererSettings.materialAlphaTest,
1318
+ layerMask: this.rendererSettings.layerMask,
1319
+ scene: this.emitter.getScene(),
1320
+ });
1321
+ }
1322
+ }