babylon.quarks 0.26.0 → 0.28.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 (83) hide show
  1. package/CHANGELOG.md +1194 -8
  2. package/README.md +13 -9
  3. package/dist/babylon.quarks.cjs +1 -1
  4. package/dist/babylon.quarks.cjs.map +1 -1
  5. package/dist/babylon.quarks.esm.js +1 -1
  6. package/dist/babylon.quarks.esm.js.map +1 -1
  7. package/dist/babylon.quarks.umd.min.js +1 -1
  8. package/dist/babylon.quarks.umd.min.js.map +1 -1
  9. package/dist/types/index.d.cts +10108 -0
  10. package/dist/types/index.d.ts +5794 -998
  11. package/package.json +18 -7
  12. package/src/BatchedRenderer.ts +274 -156
  13. package/src/EffectPool.ts +94 -12
  14. package/src/MeshSurfaceEmitter.ts +50 -14
  15. package/src/ParticleEmitter.ts +75 -17
  16. package/src/ParticleSystem.ts +981 -2066
  17. package/src/QuarksLoader.ts +666 -194
  18. package/src/QuarksPrefab.ts +367 -73
  19. package/src/QuarksSerializer.ts +512 -0
  20. package/src/QuarksUtil.ts +77 -7
  21. package/src/SpriteBatch.ts +127 -252
  22. package/src/TrailBatch.ts +545 -348
  23. package/src/VFXBatch.ts +238 -596
  24. package/src/batchKey.ts +239 -0
  25. package/src/batchSettings.ts +546 -0
  26. package/src/effectDocumentJSON.ts +261 -0
  27. package/src/effectResources.ts +76 -0
  28. package/src/envAtlas.ts +126 -18
  29. package/src/geometryUtil.ts +1 -1
  30. package/src/index.ts +69 -35
  31. package/src/nodeId.ts +36 -0
  32. package/src/shaderProgram.ts +419 -0
  33. package/src/shaderVariants.ts +245 -0
  34. package/src/shaders/chunks/blend.ts +31 -0
  35. package/src/shaders/chunks/fog.ts +12 -5
  36. package/src/shaders/chunks/sheet.ts +111 -0
  37. package/src/shaders/chunks/soft.ts +85 -0
  38. package/src/shaders/chunks/surface.ts +12 -2
  39. package/src/shaders/local_particle_physics_vert.glsl.ts +14 -28
  40. package/src/shaders/local_particle_physics_vert.wgsl.ts +16 -29
  41. package/src/shaders/particle_frag.glsl.ts +5 -22
  42. package/src/shaders/particle_frag.wgsl.ts +5 -23
  43. package/src/shaders/particle_physics_frag.glsl.ts +16 -26
  44. package/src/shaders/particle_physics_frag.wgsl.ts +14 -26
  45. package/src/shaders/particle_vert.glsl.ts +5 -27
  46. package/src/shaders/particle_vert.wgsl.ts +5 -26
  47. package/src/shaders/stretched_bb_particle_vert.glsl.ts +7 -33
  48. package/src/shaders/stretched_bb_particle_vert.wgsl.ts +7 -33
  49. package/src/shaders/trail_frag.glsl.ts +13 -0
  50. package/src/shaders/trail_frag.wgsl.ts +13 -0
  51. package/src/shaders/trail_vert.glsl.ts +45 -36
  52. package/src/shaders/trail_vert.wgsl.ts +46 -40
  53. package/src/softDepth.ts +136 -0
  54. package/src/system/BehaviorPlan.ts +85 -0
  55. package/src/system/BoundsTracker.ts +181 -0
  56. package/src/system/EmissionController.ts +358 -0
  57. package/src/system/EventDispatcher.ts +60 -0
  58. package/src/system/ParticleSpawner.ts +274 -0
  59. package/src/system/SimulationClock.ts +130 -0
  60. package/src/system/geometry.ts +104 -0
  61. package/src/system/integrate.ts +428 -0
  62. package/src/system/materialResolve.ts +165 -0
  63. package/src/system/normalize.ts +100 -0
  64. package/src/system/serialization.ts +422 -0
  65. package/src/system/settingsSchema.ts +339 -0
  66. package/src/system/types.ts +449 -0
  67. package/src/textureSource.ts +30 -0
  68. package/src/trailSettings.ts +54 -0
  69. package/src/BatchedParticleRenderer.ts +0 -3
  70. package/src/materials/ParticleMaterials.ts +0 -23
  71. package/src/materials/index.ts +0 -1
  72. package/src/shaders/chunks/index.ts +0 -1
  73. package/src/shaders/chunks/register-shader-chunks.ts +0 -32
  74. package/src/shaders/chunks/soft_fragment.glsl.ts +0 -13
  75. package/src/shaders/chunks/soft_pars_fragment.glsl.ts +0 -20
  76. package/src/shaders/chunks/soft_pars_vertex.glsl.ts +0 -6
  77. package/src/shaders/chunks/soft_vertex.glsl.ts +0 -6
  78. package/src/shaders/chunks/tile_fragment.glsl.ts +0 -9
  79. package/src/shaders/chunks/tile_pars_fragment.glsl.ts +0 -9
  80. package/src/shaders/chunks/tile_pars_vertex.glsl.ts +0 -28
  81. package/src/shaders/chunks/tile_vertex.glsl.ts +0 -18
  82. package/src/shaders/index.ts +0 -9
  83. package/src/shaders/local_particle_vert.glsl.ts +0 -50
package/src/EffectPool.ts CHANGED
@@ -1,10 +1,12 @@
1
- import {Vector3} from '@babylonjs/core/Maths/math.vector';
1
+ import {Quaternion, Vector3} from '@babylonjs/core/Maths/math.vector';
2
2
  import {TransformNode} from '@babylonjs/core/Meshes/transformNode';
3
3
  import {Node} from '@babylonjs/core/node';
4
4
  import {BatchedRenderer} from './BatchedRenderer';
5
+ import {disposeEffect} from './effectResources';
5
6
  import {ParticleSystem} from './ParticleSystem';
6
7
  import {QuarksUtil} from './QuarksUtil';
7
8
 
9
+ /** Options for a new {@link EffectPool}. */
8
10
  export interface EffectPoolOptions {
9
11
  /** The renderer every copy the pool makes is added to. */
10
12
  renderer: BatchedRenderer;
@@ -14,6 +16,31 @@ export interface EffectPoolOptions {
14
16
  maxSize?: number;
15
17
  }
16
18
 
19
+ /**
20
+ * Where {@link EffectPool.acquire} puts a copy. Each part left out is the template's own, as the
21
+ * copy was made, so nothing carries over from the copy's previous use.
22
+ */
23
+ export interface EffectPlacement {
24
+ /** Local position under {@link parent}. */
25
+ position?: Vector3;
26
+ /** Local rotation as Euler angles in radians; ignored when {@link rotationQuaternion} is set. */
27
+ rotation?: Vector3;
28
+ /** Local rotation as a quaternion, for a direction: a hit normal, a muzzle's facing. */
29
+ rotationQuaternion?: Quaternion;
30
+ /** Local scale. */
31
+ scaling?: Vector3;
32
+ /** The node the copy is parented to; none (world space) by default. */
33
+ parent?: Node | null;
34
+ }
35
+
36
+ /** The local transform a copy is put back to on every acquire. */
37
+ interface RestTransform {
38
+ position: Vector3;
39
+ rotation: Vector3;
40
+ rotationQuaternion: Quaternion | null;
41
+ scaling: Vector3;
42
+ }
43
+
17
44
  /**
18
45
  * A pool of copies of one effect, for effects played many times over: hits,
19
46
  * footsteps, muzzle flashes, pickups.
@@ -27,6 +54,10 @@ export interface EffectPoolOptions {
27
54
  * Idle copies are parked disabled, which costs the renderer nothing, and keep
28
55
  * their place in its batches, so acquiring one allocates nothing.
29
56
  *
57
+ * A copy disposed from outside (by its own `dispose()`, or with a parent it
58
+ * was acquired under) leaves the pool on its own, and the pool makes a new
59
+ * one when it needs it.
60
+ *
30
61
  * The template itself is never played by the pool; it is only cloned. Disable
31
62
  * it, or keep it out of the renderer, if it is not meant to be seen.
32
63
  */
@@ -34,6 +65,7 @@ export class EffectPool {
34
65
  private readonly idle: TransformNode[] = [];
35
66
  private readonly playing = new Set<TransformNode>();
36
67
  private readonly systemsOf = new Map<TransformNode, ParticleSystem[]>();
68
+ private readonly restOf = new Map<TransformNode, RestTransform>();
37
69
  private disposed = false;
38
70
 
39
71
  constructor(
@@ -61,10 +93,15 @@ export class EffectPool {
61
93
 
62
94
  /**
63
95
  * Takes an idle copy (making one when none is idle and the pool is under
64
- * its size), parents it, places it and starts it from the beginning.
65
- * Null when the pool is full or disposed.
96
+ * its size), places it and starts it from the beginning. The copy's
97
+ * transform is put back to the template's first, so a rotation or scale
98
+ * from its last use does not stay. Null when the pool is full or disposed.
99
+ *
100
+ * @param placement - Where to put the copy, or just its position.
101
+ * @param parent - The node to parent it to, when `placement` is a position;
102
+ * ignored otherwise (use {@link EffectPlacement.parent}).
66
103
  */
67
- acquire(position?: Vector3, parent: Node | null = null): TransformNode | null {
104
+ acquire(placement?: Vector3 | EffectPlacement, parent: Node | null = null): TransformNode | null {
68
105
  if (this.disposed) {
69
106
  return null;
70
107
  }
@@ -72,10 +109,12 @@ export class EffectPool {
72
109
  if (copy === null) {
73
110
  return null;
74
111
  }
75
- copy.parent = parent;
76
- if (position !== undefined) {
77
- copy.position.copyFrom(position);
78
- }
112
+ // Told apart by shape, not class: a vector from another copy of Babylon is still a position.
113
+ const place: EffectPlacement =
114
+ placement === undefined || typeof (placement as Vector3).x === 'number'
115
+ ? {position: placement as Vector3 | undefined, parent}
116
+ : (placement as EffectPlacement);
117
+ this.placeCopy(copy, place);
79
118
  copy.setEnabled(true);
80
119
  this.playing.add(copy);
81
120
  QuarksUtil.restart(copy);
@@ -98,6 +137,10 @@ export class EffectPool {
98
137
  if (!this.playing.delete(copy)) {
99
138
  return;
100
139
  }
140
+ if (copy.isDisposed()) {
141
+ this.forget(copy);
142
+ return;
143
+ }
101
144
  QuarksUtil.stop(copy);
102
145
  copy.setEnabled(false);
103
146
  copy.parent = null;
@@ -132,21 +175,60 @@ export class EffectPool {
132
175
  systems.push(system);
133
176
  });
134
177
  this.systemsOf.set(copy, systems);
178
+ this.restOf.set(copy, {
179
+ position: copy.position.clone(),
180
+ rotation: copy.rotation.clone(),
181
+ rotationQuaternion: copy.rotationQuaternion?.clone() ?? null,
182
+ scaling: copy.scaling.clone(),
183
+ });
135
184
  QuarksUtil.addToBatchRenderer(copy, this.options.renderer);
136
185
  copy.setEnabled(false);
137
186
  copy.parent = null;
187
+ // A copy disposed from outside, or with a parent it was placed under, is let go of.
188
+ copy.onDisposeObservable.addOnce(() => this.forget(copy));
138
189
  return copy;
139
190
  }
140
191
 
141
- private disposeCopy(copy: TransformNode): void {
142
- const systems = this.systemsOf.get(copy) ?? [];
143
- for (const system of systems) {
192
+ /** Puts the copy back to the template's transform, then applies `place`. */
193
+ private placeCopy(copy: TransformNode, place: EffectPlacement): void {
194
+ const rest = this.restOf.get(copy)!;
195
+ copy.parent = place.parent ?? null;
196
+ // By components: Babylon's copyFrom reads private fields, which a vector from elsewhere lacks.
197
+ const position = place.position ?? rest.position;
198
+ const scaling = place.scaling ?? rest.scaling;
199
+ copy.position.copyFromFloats(position.x, position.y, position.z);
200
+ copy.scaling.copyFromFloats(scaling.x, scaling.y, scaling.z);
201
+ if (place.rotationQuaternion !== undefined) {
202
+ copy.rotationQuaternion = place.rotationQuaternion.clone();
203
+ } else if (place.rotation !== undefined) {
204
+ copy.rotationQuaternion = null;
205
+ copy.rotation.copyFromFloats(place.rotation.x, place.rotation.y, place.rotation.z);
206
+ } else {
207
+ copy.rotationQuaternion = rest.rotationQuaternion?.clone() ?? null;
208
+ copy.rotation.copyFrom(rest.rotation);
209
+ }
210
+ }
211
+
212
+ /** Drops every trace of a copy, which was or is being disposed. */
213
+ private forget(copy: TransformNode): void {
214
+ for (const system of this.systemsOf.get(copy) ?? []) {
144
215
  system.removeEventListener('finished', this.onSystemFinished);
145
216
  this.options.renderer.deleteSystem(system);
146
217
  system.dispose();
147
218
  }
148
219
  this.systemsOf.delete(copy);
149
- copy.dispose();
220
+ this.restOf.delete(copy);
221
+ this.playing.delete(copy);
222
+ const index = this.idle.indexOf(copy);
223
+ if (index !== -1) {
224
+ this.idle.splice(index, 1);
225
+ }
226
+ }
227
+
228
+ private disposeCopy(copy: TransformNode): void {
229
+ this.forget(copy);
230
+ // Lets go of the textures and materials the copy shares with the template.
231
+ disposeEffect(copy);
150
232
  }
151
233
 
152
234
  /** The copy a system belongs to is done when all of its systems are. */
@@ -1,8 +1,20 @@
1
1
  import {VertexBuffer} from '@babylonjs/core/Buffers/buffer';
2
2
  import {Mesh} from '@babylonjs/core/Meshes/mesh';
3
3
  import {EmitterShape, IParticleSystem, Particle, Plugin, random, ShapeJSON, ShapeTransform, Vector3} from 'quarks.core';
4
-
4
+ import {ensureTriangleIndices} from './geometryUtil';
5
+ import {quarksNodeId} from './nodeId';
6
+
7
+ /**
8
+ * Emits from the surface of a Babylon mesh (Unity's Shape: Mesh, Unreal's Skeletal/Static Mesh
9
+ * Location): a point picked at random over the triangles, weighted by their area, moving along the
10
+ * triangle's normal at the particle's start speed. The mesh's vertices are read once, when it is
11
+ * set, in the mesh's own space, and laid in the emitter's space as they are, moved by
12
+ * {@link transform}; moving the mesh node afterwards does not move the shape. A mesh without an
13
+ * index buffer is read as a plain triangle list. A shape without a mesh emits from the origin
14
+ * along +Z.
15
+ */
5
16
  export class MeshSurfaceEmitter implements EmitterShape {
17
+ /** The shape's type in effect files. */
6
18
  type = 'mesh_surface';
7
19
 
8
20
  private _triangleIndexToArea: number[] = [];
@@ -13,29 +25,43 @@ export class MeshSurfaceEmitter implements EmitterShape {
13
25
  /** Offset, rotation and scale of the mesh within the particle system. */
14
26
  transform?: ShapeTransform;
15
27
 
16
- get mesh() {
28
+ /**
29
+ * The mesh emitted from. Setting it reads its positions and indices; a mesh without indices is
30
+ * read as a triangle list (vertices 0-1-2, 3-4-5, ...), and a mesh without positions, or with
31
+ * no whole triangle, emits from the origin.
32
+ */
33
+ get mesh(): Mesh | undefined {
17
34
  return this._mesh;
18
35
  }
19
36
 
20
37
  set mesh(mesh: Mesh | undefined) {
21
38
  this._mesh = mesh;
39
+ this._positions = undefined;
40
+ this._indices = undefined;
41
+ this._triangleIndexToArea.length = 0;
22
42
  if (!mesh) return;
23
43
 
24
44
  const positions = mesh.getVerticesData(VertexBuffer.PositionKind);
25
- const indices = mesh.getIndices();
26
- if (!positions || !indices) return;
45
+ if (!positions || positions.length < 9) return;
46
+ const vertexPositions = new Float32Array(positions);
47
+ const read = mesh.getIndices();
48
+ const indices = ensureTriangleIndices(
49
+ vertexPositions,
50
+ read && read.length > 0 ? Uint32Array.from(read) : undefined
51
+ );
52
+ const triCount = Math.floor(indices.length / 3);
53
+ if (triCount === 0) return;
54
+
55
+ this._positions = vertexPositions;
56
+ this._indices = new Int32Array(indices.subarray(0, triCount * 3));
27
57
 
28
- this._positions = new Float32Array(positions);
29
- this._indices = new Int32Array(indices);
30
-
31
- this._triangleIndexToArea.length = 0;
32
58
  let area = 0;
33
- const triCount = indices.length / 3;
34
59
  this._triangleIndexToArea.push(0);
35
60
 
36
61
  const a = new Vector3();
37
62
  const b = new Vector3();
38
63
  const c = new Vector3();
64
+ const cross = new Vector3();
39
65
 
40
66
  for (let i = 0; i < triCount; i++) {
41
67
  const i0 = indices[i * 3];
@@ -48,20 +74,25 @@ export class MeshSurfaceEmitter implements EmitterShape {
48
74
 
49
75
  b.sub(a);
50
76
  c.sub(a);
51
- const cross = new Vector3();
52
77
  cross.crossVectors(b, c);
53
78
  area += cross.length() * 0.5;
54
79
  this._triangleIndexToArea.push(area);
55
80
  }
56
81
  }
57
82
 
83
+ /**
84
+ * @param mesh The mesh to emit from.
85
+ * @param meshReferenceId The id of a mesh node in the effect file, kept until the loader links it.
86
+ * @param transform Offset, rotation and scale of the mesh within the system.
87
+ */
58
88
  constructor(mesh?: Mesh, meshReferenceId?: string, transform?: ShapeTransform) {
59
89
  this._meshReferenceId = meshReferenceId;
60
90
  this.transform = transform;
61
91
  if (mesh) this.mesh = mesh;
62
92
  }
63
93
 
64
- initialize(p: Particle) {
94
+ /** Places a new particle on the surface and sets its velocity along the triangle's normal. */
95
+ initialize(p: Particle): void {
65
96
  if (!this._positions || !this._indices) {
66
97
  p.position.set(0, 0, 0);
67
98
  p.velocity.set(0, 0, 1).multiplyScalar(p.startSpeed);
@@ -114,25 +145,30 @@ export class MeshSurfaceEmitter implements EmitterShape {
114
145
  this.transform?.apply(p);
115
146
  }
116
147
 
148
+ /** The shape in effect-file form; the mesh is written as its node id. */
117
149
  toJSON(): ShapeJSON {
118
150
  return {
119
151
  type: 'mesh_surface',
120
- mesh: this._mesh ? (this._mesh as any).uniqueId?.toString() : (this._meshReferenceId ?? ''),
152
+ mesh: this._mesh ? quarksNodeId(this._mesh) : (this._meshReferenceId ?? ''),
121
153
  ...ShapeTransform.toShapeJSON(this.transform),
122
154
  };
123
155
  }
124
156
 
125
- static fromJSON(json: any): MeshSurfaceEmitter {
157
+ /** Reads {@link toJSON} output; the mesh stays an id until `QuarksLoader` links it. */
158
+ static fromJSON(json: ShapeJSON | undefined): MeshSurfaceEmitter {
126
159
  return new MeshSurfaceEmitter(undefined, json?.mesh, ShapeTransform.fromJSON(json?.transform));
127
160
  }
128
161
 
162
+ /** A copy emitting from the same mesh. */
129
163
  clone(): EmitterShape {
130
164
  return new MeshSurfaceEmitter(this._mesh, this._meshReferenceId, this.transform?.clone());
131
165
  }
132
166
 
167
+ /** Does nothing: the shape keeps no state between steps. */
133
168
  update(_system: IParticleSystem, _delta: number): void {}
134
169
  }
135
170
 
171
+ /** Registers {@link MeshSurfaceEmitter} with quarks.core's shape table; loaded when the package is imported. */
136
172
  export const MeshSurfaceEmitterPlugin: Plugin = {
137
173
  id: 'babylon.quarks',
138
174
  initialize: () => {},
@@ -141,7 +177,7 @@ export const MeshSurfaceEmitterPlugin: Plugin = {
141
177
  type: 'mesh_surface',
142
178
  params: [['mesh', ['mesh']]],
143
179
  constructor: MeshSurfaceEmitter,
144
- loadJSON: (json: any) => MeshSurfaceEmitter.fromJSON(json),
180
+ loadJSON: (json: ShapeJSON) => MeshSurfaceEmitter.fromJSON(json),
145
181
  },
146
182
  ],
147
183
  behaviors: [],
@@ -1,8 +1,56 @@
1
1
  import {TransformNode} from '@babylonjs/core/Meshes/transformNode';
2
2
  import {Scene} from '@babylonjs/core/scene';
3
3
  import {IParticleSystem, Matrix4, SerializationOptions} from 'quarks.core';
4
+ import {quarksNodeId} from './nodeId';
5
+ import type {BabylonMetaData} from './system/types';
4
6
 
7
+ /** A {@link ParticleEmitter} as its `toJSON` writes it: the node, its transform and its system. */
8
+ export interface ParticleEmitterJSON {
9
+ /** The node's id in the effect. */
10
+ uuid: string;
11
+ /** Always `'ParticleEmitter'`. */
12
+ type: 'ParticleEmitter';
13
+ /** The node's name. */
14
+ name: string;
15
+ /** `[x, y, z]`. */
16
+ position: number[];
17
+ /** Euler angles `[x, y, z]` in radians. */
18
+ rotation: number[];
19
+ /** `[x, y, z, w]`, when the node has a rotation quaternion. */
20
+ quaternion?: number[];
21
+ /** `[x, y, z]`. */
22
+ scale: number[];
23
+ /** The node's own enabled flag. */
24
+ visible: boolean;
25
+ /** The system's JSON, as its `toJSON` writes it. */
26
+ ps: unknown;
27
+ }
28
+
29
+ /**
30
+ * Above 0 while a particle system disposes its own emitter (`ParticleSystem.dispose()`, which
31
+ * `autoDestroy` calls when the system finishes). Babylon then disposes the child nodes too, and
32
+ * child emitters keep their systems playing, so an effect's longer-lived children are not cut off
33
+ * when a short parent finishes. Disposing a node directly (`root.dispose()`) disposes its systems.
34
+ */
35
+ let systemDisposeDepth = 0;
36
+
37
+ /** Disposes `emitter` for its own system without disposing the systems of child emitters. */
38
+ export function disposeEmitterForSystem(emitter: TransformNode): void {
39
+ systemDisposeDepth++;
40
+ try {
41
+ emitter.dispose();
42
+ } finally {
43
+ systemDisposeDepth--;
44
+ }
45
+ }
46
+
47
+ /**
48
+ * The Babylon node a particle system lives at: its position, rotation and scale place the system,
49
+ * parenting it moves the effect with another node, and disabling it freezes and hides the system.
50
+ * Every {@link ParticleSystem} makes its own, as `system.emitter`.
51
+ */
5
52
  export class ParticleEmitter extends TransformNode {
53
+ /** The particle system this node carries. */
6
54
  system: IParticleSystem;
7
55
  private _matrixWorld: Matrix4 = new Matrix4();
8
56
  private _matrixWorldFlag = -1;
@@ -40,6 +88,7 @@ export class ParticleEmitter extends TransformNode {
40
88
  return this._matrixWorld;
41
89
  }
42
90
 
91
+ /** Whether the node and its parents are enabled; setting it is `setEnabled`. */
43
92
  get visible(): boolean {
44
93
  return this.isEnabled();
45
94
  }
@@ -48,8 +97,9 @@ export class ParticleEmitter extends TransformNode {
48
97
  this.setEnabled(value);
49
98
  }
50
99
 
100
+ /** The id effect files know this emitter by; see {@link quarksNodeId}. */
51
101
  get uuid(): string {
52
- return this.uniqueId.toString();
102
+ return quarksNodeId(this);
53
103
  }
54
104
 
55
105
  constructor(system: IParticleSystem, scene?: Scene) {
@@ -57,20 +107,22 @@ export class ParticleEmitter extends TransformNode {
57
107
  this.system = system;
58
108
  }
59
109
 
60
- dispose(): void {
61
- super.dispose();
62
- }
63
-
64
- extractFromCache(cache: any): any[] {
65
- const values: any[] = [];
66
- for (const key in cache) {
67
- const data = cache[key];
68
- if (data && typeof data === 'object') {
69
- delete data.metadata;
70
- }
71
- values.push(data);
110
+ /**
111
+ * Disposes the node and the particle system it carries, so `root.dispose()` on an effect tree
112
+ * also removes its systems from their renderer. Safe to call more than once. When a system
113
+ * disposes its own emitter (`ParticleSystem.dispose()`, `autoDestroy`), the systems of child
114
+ * emitters keep playing until they finish or are disposed themselves.
115
+ */
116
+ dispose(doNotRecurse?: boolean, disposeMaterialAndTextures = false): void {
117
+ if (this.isDisposed()) {
118
+ return;
72
119
  }
73
- return values;
120
+ super.dispose(doNotRecurse, disposeMaterialAndTextures);
121
+ if (systemDisposeDepth > 0) {
122
+ return;
123
+ }
124
+ // IParticleSystem (quarks.core) has no dispose(); ParticleSystem does.
125
+ (this.system as IParticleSystem & {dispose?: () => void}).dispose?.();
74
126
  }
75
127
 
76
128
  clone(name?: string): ParticleEmitter {
@@ -88,9 +140,15 @@ export class ParticleEmitter extends TransformNode {
88
140
  return clonedEmitter;
89
141
  }
90
142
 
91
- toJSON(meta?: any, options: SerializationOptions = {}): any {
143
+ /**
144
+ * The node and its system in effect-file form: one entry of an effect file's `object` tree,
145
+ * without its children. Geometry, materials and textures go into `meta` when it is given.
146
+ * `visible` is the node's own enabled flag, not the one it inherits from its parents. To write
147
+ * a whole effect, with its children and resources, use `serializeEffect`.
148
+ */
149
+ toJSON(meta?: BabylonMetaData, options: SerializationOptions = {}): ParticleEmitterJSON {
92
150
  return {
93
- uuid: (this as any)._quarksUUID ?? this.uniqueId.toString(),
151
+ uuid: quarksNodeId(this),
94
152
  type: 'ParticleEmitter',
95
153
  name: this.name,
96
154
  position: [this.position.x, this.position.y, this.position.z],
@@ -104,7 +162,7 @@ export class ParticleEmitter extends TransformNode {
104
162
  ]
105
163
  : undefined,
106
164
  scale: [this.scaling.x, this.scaling.y, this.scaling.z],
107
- visible: this.isEnabled(),
165
+ visible: this.isEnabled(false),
108
166
  ps: this.system.toJSON(meta, options),
109
167
  };
110
168
  }