babylon.quarks 0.17.0

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