babylon.quarks 0.27.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 (52) hide show
  1. package/CHANGELOG.md +425 -0
  2. package/README.md +3 -1
  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 +4256 -831
  10. package/dist/types/index.d.ts +4256 -831
  11. package/package.json +7 -2
  12. package/src/BatchedRenderer.ts +93 -78
  13. package/src/EffectPool.ts +91 -12
  14. package/src/MeshSurfaceEmitter.ts +24 -13
  15. package/src/ParticleEmitter.ts +31 -3
  16. package/src/ParticleSystem.ts +92 -25
  17. package/src/QuarksLoader.ts +289 -171
  18. package/src/QuarksPrefab.ts +301 -94
  19. package/src/QuarksSerializer.ts +43 -15
  20. package/src/QuarksUtil.ts +64 -9
  21. package/src/SpriteBatch.ts +21 -6
  22. package/src/TrailBatch.ts +12 -6
  23. package/src/VFXBatch.ts +70 -528
  24. package/src/batchKey.ts +4 -2
  25. package/src/batchSettings.ts +546 -0
  26. package/src/effectDocumentJSON.ts +261 -0
  27. package/src/effectResources.ts +21 -24
  28. package/src/envAtlas.ts +8 -2
  29. package/src/geometryUtil.ts +1 -1
  30. package/src/index.ts +39 -4
  31. package/src/nodeId.ts +25 -2
  32. package/src/shaderProgram.ts +110 -16
  33. package/src/shaderVariants.ts +24 -4
  34. package/src/shaders/chunks/blend.ts +31 -0
  35. package/src/shaders/chunks/fog.ts +8 -3
  36. package/src/shaders/chunks/soft.ts +6 -5
  37. package/src/shaders/chunks/surface.ts +2 -2
  38. package/src/shaders/particle_frag.glsl.ts +3 -1
  39. package/src/shaders/particle_frag.wgsl.ts +2 -0
  40. package/src/shaders/particle_physics_frag.glsl.ts +5 -2
  41. package/src/shaders/particle_physics_frag.wgsl.ts +2 -0
  42. package/src/shaders/trail_frag.glsl.ts +4 -1
  43. package/src/shaders/trail_frag.wgsl.ts +2 -0
  44. package/src/system/EmissionController.ts +2 -4
  45. package/src/system/EventDispatcher.ts +26 -10
  46. package/src/system/geometry.ts +22 -29
  47. package/src/system/materialResolve.ts +56 -7
  48. package/src/system/normalize.ts +6 -1
  49. package/src/system/serialization.ts +54 -33
  50. package/src/system/settingsSchema.ts +32 -10
  51. package/src/system/types.ts +67 -28
  52. package/src/trailSettings.ts +9 -6
@@ -1,5 +1,5 @@
1
1
  import type {BaseTexture} from '@babylonjs/core/Materials/Textures/baseTexture';
2
- import type {Texture} from '@babylonjs/core/Materials/Textures/texture';
2
+ import {Texture} from '@babylonjs/core/Materials/Textures/texture';
3
3
  import type {VFXBatchSettings} from '../BatchedRenderer';
4
4
 
5
5
  /** Settings given next to a material, which win over what is read from it. */
@@ -14,6 +14,52 @@ export interface MaterialOverrides {
14
14
  layerMask?: number;
15
15
  }
16
16
 
17
+ /**
18
+ * What a {@link ParticleSystem} reads its draw settings from: a Babylon material (`StandardMaterial`,
19
+ * `PBRMaterial`, a node material, …), or a material entry of an effect file once its texture slots
20
+ * are resolved to textures, as `ParticleSystem.fromJSON` does. Each field is read only when it is
21
+ * there, so any material fits; the particles are still drawn with quarks' own shaders. The particle
22
+ * texture is the first slot that holds a `Texture`.
23
+ */
24
+ export interface ParticleMaterialSource {
25
+ /** A PBR material's base colour texture. */
26
+ albedoTexture?: BaseTexture | null;
27
+ /** A standard material's diffuse texture. */
28
+ diffuseTexture?: BaseTexture | null;
29
+ /** An emissive texture, read when there is no albedo or diffuse texture. */
30
+ emissiveTexture?: BaseTexture | null;
31
+ /** An opacity texture, read when there is none of the above. */
32
+ opacityTexture?: BaseTexture | null;
33
+ /** A base texture, read last. */
34
+ baseTexture?: BaseTexture | null;
35
+ /** The reflection texture; a cube is drawn through a 3×2 atlas built from it. */
36
+ reflectionTexture?: BaseTexture | null;
37
+ /** A PBR material's environment texture, read when there is no reflection texture. */
38
+ environmentTexture?: BaseTexture | null;
39
+ /** How strong the reflection is; the reflection texture's `level` when unset. */
40
+ reflectionLevel?: number;
41
+ /** A 3×2 env atlas for the reflection; the system stores the atlas it builds from a cube here. */
42
+ reflectionAtlas?: BaseTexture | null;
43
+ /** A Babylon alpha mode constant. */
44
+ alphaMode?: number;
45
+ /** Whether the material blends; decides `transparent`. */
46
+ needAlphaBlending?(): boolean;
47
+ /** Opacity, read for `transparent` when there is no `needAlphaBlending`. */
48
+ alpha?: number;
49
+ /** Inverted into `depthTest`. */
50
+ disableDepthTest?: boolean;
51
+ /** Inverted into `depthWrite`. */
52
+ disableDepthWrite?: boolean;
53
+ /** Read for `depthWrite` when there is no `disableDepthWrite`. */
54
+ forceDepthWrite?: boolean;
55
+ /** The alpha test cutoff is read only in an alpha-test mode (1 or 3). */
56
+ transparencyMode?: number | null;
57
+ /** The alpha test cutoff. */
58
+ alphaCutOff?: number;
59
+ /** The alpha test cutoff, as some materials name it. */
60
+ alphaCutOffValue?: number;
61
+ }
62
+
17
63
  /** The draw settings a {@link ParticleSystem} takes from a material, before the env atlas is built. */
18
64
  export interface ResolvedMaterialSettings {
19
65
  texture: Texture | null;
@@ -39,18 +85,21 @@ export function liveAtlas<T extends BaseTexture>(atlas: T | null | undefined): T
39
85
  * material does not have keeps its `current` value.
40
86
  */
41
87
  export function resolveMaterialSettings(
42
- material: any,
88
+ material: ParticleMaterialSource | null | undefined,
43
89
  overrides: MaterialOverrides,
44
90
  current: VFXBatchSettings
45
91
  ): ResolvedMaterialSettings {
92
+ // The particle texture is a `Texture`: the first slot that holds one, so a cube in a slot is passed over.
46
93
  const texture =
47
94
  overrides.texture !== undefined
48
95
  ? overrides.texture
49
- : (material?.albedoTexture ??
50
- material?.diffuseTexture ??
51
- material?.emissiveTexture ??
52
- material?.opacityTexture ??
53
- material?.baseTexture ??
96
+ : ([
97
+ material?.albedoTexture,
98
+ material?.diffuseTexture,
99
+ material?.emissiveTexture,
100
+ material?.opacityTexture,
101
+ material?.baseTexture,
102
+ ].find((slot): slot is Texture => slot instanceof Texture) ??
54
103
  current.texture ??
55
104
  null);
56
105
  const reflectionTexture =
@@ -1,5 +1,5 @@
1
1
  import {ConstantValue, FunctionValueGenerator, ValueGenerator, Vector2, Vector3} from 'quarks.core';
2
- import type {MeshLighting, SortMode} from '../VFXBatch';
2
+ import type {MeshLighting, RenderFace, SortMode} from '../batchSettings';
3
3
  import type {BurstParameters, CullingMode} from './types';
4
4
 
5
5
  // Normalisers shared by the ParticleSystem constructor, its setters and its JSON reader, so a
@@ -15,6 +15,11 @@ export function validSortMode(mode: unknown): SortMode {
15
15
  return mode === 'distance' || mode === 'oldestInFront' || mode === 'youngestInFront' ? mode : 'none';
16
16
  }
17
17
 
18
+ /** `face` if it is one of the render faces, else `'both'`. */
19
+ export function validRenderFace(face: unknown): RenderFace {
20
+ return face === 'front' || face === 'back' ? face : 'both';
21
+ }
22
+
18
23
  /** `mode` if it is one of the culling modes, else `'always'`. */
19
24
  export function validCullingMode(mode: unknown): CullingMode {
20
25
  return mode === 'pause' || mode === 'pauseAndCatchUp' ? mode : 'always';
@@ -3,14 +3,20 @@ import type {BaseTexture} from '@babylonjs/core/Materials/Textures/baseTexture';
3
3
  import {Texture} from '@babylonjs/core/Materials/Textures/texture';
4
4
  import type {Scene} from '@babylonjs/core/scene';
5
5
  import {
6
+ BehaviorToJSON,
6
7
  ConstantValue,
7
8
  EmitterFromJSON,
9
+ EmitterToJSON,
10
+ readFormatPart,
8
11
  RendererEmitterSettings,
9
12
  StretchedBillBoardSettings,
10
13
  TrailSettings,
11
14
  ValueGeneratorFromJSON,
12
15
  Vector3,
16
+ Vector3ArrayJSON,
17
+ warnFormat,
13
18
  } from 'quarks.core';
19
+ import type {MaterialTextureRef} from '../effectDocumentJSON';
14
20
  import type {ParticleSystem} from '../ParticleSystem';
15
21
  import {
16
22
  trailCurveTension,
@@ -19,6 +25,7 @@ import {
19
25
  trailTessellation,
20
26
  trailTextureMode,
21
27
  trailTextureScale,
28
+ type TrailOptions,
22
29
  } from '../trailSettings';
23
30
  import {
24
31
  EROSION_DEFAULT_SOFTNESS,
@@ -28,8 +35,15 @@ import {
28
35
  UV_DISTORTION_DEFAULT_UV,
29
36
  } from '../VFXBatch';
30
37
  import {resolveGeometryData} from './geometry';
38
+ import type {ParticleMaterialSource} from './materialResolve';
31
39
  import {settingsFromJSON, settingsToJSON} from './settingsSchema';
32
- import type {BabylonMetaData, ParticleSystemJSONParameters, ParticleSystemParameters} from './types';
40
+ import type {
41
+ BabylonMetaData,
42
+ ParticleSystemJSONParameters,
43
+ ParticleSystemParameters,
44
+ RendererEmitterSettingsJSON,
45
+ TrailSettingsJSON,
46
+ } from './types';
33
47
 
34
48
  // Reading and writing a ParticleSystem's JSON. The class's toJSON and fromJSON call these.
35
49
 
@@ -49,8 +63,8 @@ function nextSerializationId(meta: BabylonMetaData, prefix: string): string {
49
63
  }
50
64
 
51
65
  /** The optional trail flags that are switched on, so JSON and clones only carry the ones in use. */
52
- export function trailFlags(settings: TrailSettings | undefined): Partial<TrailSettings> {
53
- const flags: Partial<TrailSettings> = {};
66
+ export function trailFlags(settings: Partial<TrailOptions> | undefined): Partial<TrailOptions> {
67
+ const flags: Partial<TrailOptions> = {};
54
68
  if (settings?.uvFromHead) flags.uvFromHead = true;
55
69
  if (settings?.colorFromParticle) flags.colorFromParticle = true;
56
70
  if (settings?.dieWithParticle) flags.dieWithParticle = true;
@@ -59,8 +73,8 @@ export function trailFlags(settings: TrailSettings | undefined): Partial<TrailSe
59
73
  }
60
74
 
61
75
  /** A trail's tessellation and curve tension, normalised, each only when it differs from the default. */
62
- export function trailCurve(settings: TrailSettings | undefined): Partial<TrailSettings> {
63
- const curve: Partial<TrailSettings> = {};
76
+ export function trailCurve(settings: Partial<TrailOptions> | undefined): Partial<TrailOptions> {
77
+ const curve: Partial<TrailOptions> = {};
64
78
  const tessellation = trailTessellation(settings);
65
79
  const curveTension = trailCurveTension(settings);
66
80
  if (tessellation > 0) curve.tessellation = tessellation;
@@ -72,8 +86,8 @@ export function trailCurve(settings: TrailSettings | undefined): Partial<TrailSe
72
86
  * A trail's texture mode and scale, minimum vertex distance and lifetime, normalised, each only
73
87
  * when it differs from the default.
74
88
  */
75
- export function trailSampling(settings: TrailSettings | undefined): Partial<TrailSettings> {
76
- const out: Partial<TrailSettings> = {};
89
+ export function trailSampling(settings: Partial<TrailOptions> | undefined): Partial<TrailOptions> {
90
+ const out: Partial<TrailOptions> = {};
77
91
  const textureMode = trailTextureMode(settings);
78
92
  const textureScale = trailTextureScale(settings);
79
93
  const minVertexDistance = trailMinVertexDistance(settings);
@@ -86,12 +100,12 @@ export function trailSampling(settings: TrailSettings | undefined): Partial<Trai
86
100
  }
87
101
 
88
102
  /** A trail's `followLocalOrigin`, only when on: off is the default. */
89
- export function trailFollowLocalOrigin(settings: TrailSettings | undefined): Partial<TrailSettings> {
103
+ export function trailFollowLocalOrigin(settings: Partial<TrailOptions> | undefined): Partial<TrailOptions> {
90
104
  return settings?.followLocalOrigin ? {followLocalOrigin: true} : {};
91
105
  }
92
106
 
93
107
  /** A trail's side axis as JSON `[x, y, z]`, or nothing when unset. */
94
- function trailSideAxisJSON(settings: TrailSettings | undefined): {sideAxis?: number[]} {
108
+ function trailSideAxisJSON(settings: TrailSettings | undefined): {sideAxis?: Vector3ArrayJSON} {
95
109
  const axis = settings?.sideAxis;
96
110
  return axis ? {sideAxis: [axis.x, axis.y, axis.z]} : {};
97
111
  }
@@ -120,7 +134,7 @@ function trailSideAxisFromJSON(value: unknown): {sideAxis?: Vector3} {
120
134
  if (Number.isFinite(nx) && Number.isFinite(ny) && Number.isFinite(nz) && nx * nx + ny * ny + nz * nz > 0) {
121
135
  return {sideAxis: new Vector3(nx, ny, nz)};
122
136
  }
123
- console.warn(`[quarks] sideAxis ${JSON.stringify(value)} is not a direction and is ignored`);
137
+ warnFormat(`sideAxis ${JSON.stringify(value)} is not a direction and is ignored`);
124
138
  return {};
125
139
  }
126
140
 
@@ -149,15 +163,17 @@ export function particleSystemToJSON(
149
163
 
150
164
  // Only what differs from the defaults the constructor and fromJSON give a setting left out.
151
165
  // The shape, the settings the table marks `always`, the geometry, the material and the
152
- // behaviors are always written: the loaders of 0.27 and earlier, and three.quarks, need them.
166
+ // behaviors list are always written: the loaders of 0.27 and earlier need them. Within the
167
+ // shape and each behavior, only the fields that differ from their defaults, which 0.27 reads
168
+ // and three.quarks may not (see docs/FORMAT.md).
153
169
  return {
154
170
  version: '3.0',
155
- shape: system.emitterShape.toJSON(),
171
+ shape: EmitterToJSON(system.emitterShape),
156
172
  ...settingsToJSON(system),
157
173
  instancingGeometry: geometryUUID,
158
- ...(Object.keys(rendererSettingsJSON).length > 0 ? {rendererEmitterSettings: rendererSettingsJSON} : {}),
174
+ ...(rendererSettingsJSON ? {rendererEmitterSettings: rendererSettingsJSON} : {}),
159
175
  material: materialUUID,
160
- behaviors: system.behaviors.map((b) => b.toJSON()),
176
+ behaviors: system.behaviors.map((b) => BehaviorToJSON(b, system)),
161
177
  };
162
178
  }
163
179
 
@@ -172,10 +188,10 @@ export function particleSystemParametersFromJSON(
172
188
  scene?: Scene
173
189
  ): ParticleSystemParameters {
174
190
  // A part the file leaves out takes the constructor's default, as a system made in code does.
175
- const shape = json.shape ? EmitterFromJSON(json.shape, meta as any) : undefined;
191
+ const shape = json.shape ? readFormatPart('shape', () => EmitterFromJSON(json.shape!, meta)) : undefined;
176
192
  let rendererEmitterSettings: RendererEmitterSettings;
177
193
  if (json.renderMode === RenderMode.Trail) {
178
- const trailSettings = json.rendererEmitterSettings as TrailSettings;
194
+ const trailSettings = json.rendererEmitterSettings as TrailSettingsJSON | undefined;
179
195
  rendererEmitterSettings = {
180
196
  startLength:
181
197
  trailSettings?.startLength !== undefined
@@ -185,26 +201,31 @@ export function particleSystemParametersFromJSON(
185
201
  ...trailFlags(trailSettings),
186
202
  ...trailCurve(trailSettings),
187
203
  ...trailSampling(trailSettings),
188
- ...trailSideAxisFromJSON((trailSettings as {sideAxis?: unknown} | undefined)?.sideAxis),
204
+ ...trailSideAxisFromJSON(trailSettings?.sideAxis),
189
205
  };
190
206
  } else if (json.renderMode === RenderMode.StretchedBillBoard) {
191
- rendererEmitterSettings = {...(json.rendererEmitterSettings ?? {})};
207
+ // The render mode says which settings the file holds.
208
+ const stretched: Partial<StretchedBillBoardSettings> = {
209
+ ...(json.rendererEmitterSettings as StretchedBillBoardSettings | undefined),
210
+ };
192
211
  if (json.speedFactor !== undefined) {
193
- (rendererEmitterSettings as StretchedBillBoardSettings).speedFactor = json.speedFactor;
212
+ stretched.speedFactor = json.speedFactor;
194
213
  }
214
+ rendererEmitterSettings = stretched;
195
215
  } else {
196
216
  rendererEmitterSettings = {};
197
217
  }
198
218
 
199
219
  const materialMeta = json.material ? meta.materials?.[json.material] : undefined;
200
- const materialTextureRef = materialMeta?.texture ?? json.texture;
201
- const texture =
202
- typeof materialTextureRef === 'string'
203
- ? (meta.textures?.[materialTextureRef] ?? null)
204
- : (materialTextureRef ?? null);
205
- // Texture effects reference textures by uuid in a serialised meta, or hold them once a loader resolved them.
206
- const resolveTexture = (ref: unknown): BaseTexture | null =>
207
- typeof ref === 'string' ? (meta.textures?.[ref] ?? null) : ((ref as BaseTexture | null) ?? null);
220
+ // Texture slots reference textures by uuid in a serialised meta, or hold them once a loader resolved them.
221
+ const resolveTexture = (ref: MaterialTextureRef | undefined): BaseTexture | null =>
222
+ typeof ref === 'string' ? (meta.textures?.[ref] ?? null) : (ref ?? null);
223
+ const mainTexture = resolveTexture(materialMeta?.texture ?? json.texture);
224
+ // Particles sample a 2D texture.
225
+ const texture = mainTexture instanceof Texture ? mainTexture : null;
226
+ const material: ParticleMaterialSource | undefined =
227
+ materialMeta?.sourceMaterial ??
228
+ (materialMeta ? {...materialMeta, reflectionAtlas: resolveTexture(materialMeta.reflectionAtlas)} : undefined);
208
229
  const erosionMeta = materialMeta?.erosion;
209
230
  const gradientMeta = materialMeta?.gradientMap;
210
231
  const distortionMeta = materialMeta?.uvDistortion;
@@ -226,7 +247,7 @@ export function particleSystemParametersFromJSON(
226
247
  instancingNormals: resolvedGeometry.normals,
227
248
  rendererEmitterSettings,
228
249
  texture,
229
- material: materialMeta?.sourceMaterial ?? materialMeta,
250
+ material,
230
251
  // The material entry wins over the system's own fields, which older files wrote too.
231
252
  blendMode: materialMeta?.alphaMode ?? settings.blendMode ?? Constants.ALPHA_ADD,
232
253
  transparent: materialMeta?.transparent ?? settings.transparent ?? true,
@@ -253,7 +274,7 @@ export function particleSystemParametersFromJSON(
253
274
  }
254
275
 
255
276
  /** The render mode's own settings as JSON: a trail's or a stretched billboard's, else none. */
256
- function rendererEmitterSettingsJSON(system: ParticleSystem): RendererEmitterSettings {
277
+ function rendererEmitterSettingsJSON(system: ParticleSystem): RendererEmitterSettingsJSON | undefined {
257
278
  if (system.renderMode === RenderMode.Trail) {
258
279
  const settings = system.rendererEmitterSettings as TrailSettings;
259
280
  // `startLength` always, for loaders that read it without a default; the rest only when on.
@@ -263,8 +284,8 @@ function rendererEmitterSettingsJSON(system: ParticleSystem): RendererEmitterSet
263
284
  ...trailFlags(settings),
264
285
  ...trailCurve(settings),
265
286
  ...trailSampling(settings),
266
- ...(trailSideAxisJSON(settings) as {sideAxis?: any}),
267
- } as TrailSettings;
287
+ ...trailSideAxisJSON(settings),
288
+ };
268
289
  }
269
290
  if (system.renderMode === RenderMode.StretchedBillBoard) {
270
291
  return {
@@ -272,7 +293,7 @@ function rendererEmitterSettingsJSON(system: ParticleSystem): RendererEmitterSet
272
293
  lengthFactor: (system.rendererEmitterSettings as StretchedBillBoardSettings).lengthFactor,
273
294
  };
274
295
  }
275
- return {};
296
+ return undefined;
276
297
  }
277
298
 
278
299
  /** Writes the system's geometry into `meta` once per document, and returns its uuid. */
@@ -337,7 +358,7 @@ function materialMeta(system: ParticleSystem, meta: BabylonMetaData): string {
337
358
  if (uuid === undefined) {
338
359
  uuid = nextSerializationId(meta, 'quarks_texture');
339
360
  textures.set(value, uuid);
340
- meta.textures[uuid] = value as Texture;
361
+ meta.textures[uuid] = value;
341
362
  }
342
363
  return uuid;
343
364
  };
@@ -1,4 +1,5 @@
1
1
  import {Constants} from '@babylonjs/core/Engines/constants';
2
+ import type {ColorGeneratorJSON, RotationGeneratorJSON, ValueGeneratorJSON, Vector3GeneratorJSON} from 'quarks.core';
2
3
  import {
3
4
  ColorGenerator,
4
5
  ColorGeneratorFromJSON,
@@ -7,6 +8,7 @@ import {
7
8
  FunctionColorGenerator,
8
9
  FunctionValueGenerator,
9
10
  GeneratorFromJSON,
11
+ readFormatPart,
10
12
  RotationGenerator,
11
13
  ValueGenerator,
12
14
  ValueGeneratorFromJSON,
@@ -17,8 +19,20 @@ import {
17
19
  } from 'quarks.core';
18
20
  import type {ParticleSystem} from '../ParticleSystem';
19
21
  import {RenderMode} from '../VFXBatch';
20
- import {DEFAULT_SOFT_FAR_FADE, normalizeBursts, validCullingMode, validSortMode, validTileCount} from './normalize';
21
- import type {BurstParameters, ParticleSystemJSONParameters, ParticleSystemParameters} from './types';
22
+ import {
23
+ DEFAULT_SOFT_FAR_FADE,
24
+ normalizeBursts,
25
+ validCullingMode,
26
+ validRenderFace,
27
+ validSortMode,
28
+ validTileCount,
29
+ } from './normalize';
30
+ import type {
31
+ BurstParameters,
32
+ BurstParametersJSON,
33
+ ParticleSystemJSONParameters,
34
+ ParticleSystemParameters,
35
+ } from './types';
22
36
 
23
37
  /**
24
38
  * One setting of a {@link ParticleSystem} that is a plain value or a generator: how to read it
@@ -54,6 +68,9 @@ export interface SettingDescriptor {
54
68
 
55
69
  type Generator = {toJSON(): unknown; clone(): unknown};
56
70
 
71
+ /** A burst as files hold it, any field left out. */
72
+ type BurstJSON = Partial<BurstParametersJSON>;
73
+
57
74
  /** A number, boolean or string compared by value. */
58
75
  function plain<T>(
59
76
  param: keyof ParticleSystemParameters,
@@ -97,11 +114,11 @@ function boolean(json: unknown): boolean | undefined {
97
114
  * A generator, compared with its default through its JSON. `read` turns the JSON back into one;
98
115
  * a bare number is read as a constant.
99
116
  */
100
- function generator(
117
+ function generator<J>(
101
118
  param: keyof ParticleSystemParameters,
102
119
  get: (system: ParticleSystem) => Generator,
103
120
  defaultValue: () => Generator,
104
- read: (json: any) => unknown,
121
+ read: (json: J) => unknown,
105
122
  always = true
106
123
  ): SettingDescriptor {
107
124
  const defaultJSON = JSON.stringify(defaultValue().toJSON());
@@ -111,7 +128,8 @@ function generator(
111
128
  get,
112
129
  isDefault: (value) => JSON.stringify((value as Generator).toJSON()) === defaultJSON,
113
130
  toJSON: (value) => (value as Generator).toJSON(),
114
- fromJSON: (json) => (typeof json === 'number' ? new ConstantValue(json) : read(json)),
131
+ // The generator readers check the JSON they are given, and warn about what they cannot read.
132
+ fromJSON: (json) => (typeof json === 'number' ? new ConstantValue(json) : read(json as J)),
115
133
  clone: (value) => (value as Generator).clone(),
116
134
  };
117
135
  }
@@ -152,19 +170,21 @@ export const SETTINGS: ReadonlyArray<SettingDescriptor> = [
152
170
  'startRotation',
153
171
  (s) => s.startRotation,
154
172
  () => new ConstantValue(0),
155
- (json) => GeneratorFromJSON(json) as RotationGenerator | ValueGenerator | FunctionValueGenerator
173
+ (json: ValueGeneratorJSON | RotationGeneratorJSON) =>
174
+ GeneratorFromJSON(json) as RotationGenerator | ValueGenerator | FunctionValueGenerator
156
175
  ),
157
176
  generator(
158
177
  'startSize',
159
178
  (s) => s.startSize,
160
179
  () => new ConstantValue(1),
161
- (json) => GeneratorFromJSON(json) as Vector3Generator | ValueGenerator | FunctionValueGenerator
180
+ (json: ValueGeneratorJSON | Vector3GeneratorJSON) =>
181
+ GeneratorFromJSON(json) as Vector3Generator | ValueGenerator | FunctionValueGenerator
162
182
  ),
163
183
  generator(
164
184
  'startColor',
165
185
  (s) => s.startColor,
166
186
  () => new ConstantColor(new Vector4(1, 1, 1, 1)),
167
- (json) => ColorGeneratorFromJSON(json) as ColorGenerator | FunctionColorGenerator
187
+ (json: ColorGeneratorJSON) => ColorGeneratorFromJSON(json) as ColorGenerator | FunctionColorGenerator
168
188
  ),
169
189
  generator(
170
190
  'emissionOverTime',
@@ -193,7 +213,7 @@ export const SETTINGS: ReadonlyArray<SettingDescriptor> = [
193
213
  fromJSON: (json) =>
194
214
  Array.isArray(json)
195
215
  ? normalizeBursts(
196
- json.map((burst: any) => ({
216
+ json.map((burst: BurstJSON) => ({
197
217
  time: burst.time,
198
218
  count:
199
219
  typeof burst.count === 'number'
@@ -228,6 +248,7 @@ export const SETTINGS: ReadonlyArray<SettingDescriptor> = [
228
248
  ),
229
249
  plain('seed', (s) => s.seed, undefined, number),
230
250
  plain('sortMode', (s) => s.sortMode, 'none', validSortMode),
251
+ plain('renderFace', (s) => s.renderFace, 'both', validRenderFace),
231
252
  plain(
232
253
  'alignToDirection',
233
254
  (s) => s.alignToDirection,
@@ -299,7 +320,8 @@ export function settingsFromJSON(json: ParticleSystemJSONParameters): Partial<Pa
299
320
  if (value === undefined || value === null) {
300
321
  continue;
301
322
  }
302
- const read = setting.fromJSON(value);
323
+ const key = settingJSONKey(setting);
324
+ const read = readFormatPart(key, () => setting.fromJSON(value));
303
325
  if (read !== undefined) {
304
326
  parameters[setting.param] = read;
305
327
  }
@@ -1,18 +1,27 @@
1
+ import type {BaseTexture} from '@babylonjs/core/Materials/Textures/baseTexture';
1
2
  import type {Texture} from '@babylonjs/core/Materials/Textures/texture';
2
3
  import type {Scene} from '@babylonjs/core/scene';
3
4
  import type {
4
5
  Behavior,
6
+ BehaviorJSON,
5
7
  ColorGenerator,
8
+ ColorGeneratorJSON,
6
9
  EmitterShape,
7
10
  FunctionColorGenerator,
8
- FunctionJSON,
9
11
  FunctionValueGenerator,
10
12
  RendererEmitterSettings,
11
13
  RotationGenerator,
14
+ RotationGeneratorJSON,
15
+ ShapeJSON,
16
+ StretchedBillBoardSettings,
12
17
  ValueGenerator,
18
+ ValueGeneratorJSON,
13
19
  Vector2,
14
20
  Vector3,
21
+ Vector3ArrayJSON,
15
22
  Vector3Generator,
23
+ Vector3GeneratorJSON,
24
+ Vector3JSON,
16
25
  } from 'quarks.core';
17
26
  import type {
18
27
  MeshLighting,
@@ -20,10 +29,28 @@ import type {
20
29
  ParticleErosionOptions,
21
30
  ParticleGradientMap,
22
31
  ParticleUVDistortionOptions,
32
+ RenderFace,
23
33
  RenderMode,
24
34
  SortMode,
25
35
  TextureUV,
26
36
  } from '../VFXBatch';
37
+ import type {GeometryEntry, QuarksMaterialJSON} from '../effectDocumentJSON';
38
+ import type {TrailOptions} from '../trailSettings';
39
+ import type {ParticleMaterialSource} from './materialResolve';
40
+
41
+ /** A trail's settings in JSON, each only when not its default: the start length as a generator's JSON, the side axis as `[x, y, z]`. */
42
+ export type TrailSettingsJSON = Partial<TrailOptions> & {
43
+ /** See `TrailSettings.startLength`. */
44
+ startLength?: ValueGeneratorJSON;
45
+ /** See `TrailSettings.sideAxis`; files may hold it as `{x, y, z}`. */
46
+ sideAxis?: Vector3ArrayJSON | Vector3JSON;
47
+ };
48
+
49
+ /**
50
+ * A render mode's own settings in JSON, see {@link RendererEmitterSettings}: a trail's or a
51
+ * stretched billboard's. The other render modes have none, and their systems leave the field out.
52
+ */
53
+ export type RendererEmitterSettingsJSON = TrailSettingsJSON | StretchedBillBoardSettings;
27
54
 
28
55
  /**
29
56
  * One burst of a system's emission (Unity's Emission Bursts): `count` particles at `time` seconds
@@ -64,6 +91,12 @@ export interface BurstParameters {
64
91
  * An empty system is never culled, so it can start emitting and grow the
65
92
  * bounds that decide its visibility. A system another one emits into is never
66
93
  * culled either; its parent decides.
94
+ *
95
+ * The cameras tested are the scene's active cameras (`scene.activeCameras`, or
96
+ * `scene.activeCamera`), each only for the systems on a layer it renders
97
+ * (`camera.layerMask & system.layers.mask`). A camera that renders into a
98
+ * render target texture (a mirror, a minimap, a portal) is not one of them: a
99
+ * system seen only through one should keep `'always'`.
67
100
  */
68
101
  export type CullingMode = 'always' | 'pause' | 'pauseAndCatchUp';
69
102
 
@@ -169,7 +202,7 @@ export interface ParticleSystemParameters {
169
202
  /** @deprecated Set `speedFactor` in {@link rendererEmitterSettings} instead; this one wins when both are set. */
170
203
  speedFactor?: number;
171
204
  /** See {@link ParticleSystem.material}. Default none. */
172
- material?: unknown;
205
+ material?: ParticleMaterialSource | null;
173
206
  /** See {@link ParticleSystem.texture}. Unset takes the material's texture; null draws without one. */
174
207
  texture?: Texture | null;
175
208
  /** See {@link ParticleSystem.startTileIndex}. Default 0. */
@@ -236,6 +269,8 @@ export interface ParticleSystemParameters {
236
269
  seed?: number;
237
270
  /** Draw order within the batch, see {@link ParticleSystem.sortMode}. Default `'none'`. */
238
271
  sortMode?: SortMode;
272
+ /** Which faces a mesh particle draws, see {@link ParticleSystem.renderFace}. Default `'both'`. */
273
+ renderFace?: RenderFace;
239
274
  /** Whether particles start turned along their direction of travel, see {@link ParticleSystem.alignToDirection}. Default false. */
240
275
  alignToDirection?: boolean;
241
276
  /** Smallest on-screen size, as a fraction of the viewport height, see {@link ParticleSystem.minParticleSize}. Default 0. */
@@ -255,13 +290,15 @@ export interface BurstParametersJSON {
255
290
  /** See {@link BurstParameters.time}. */
256
291
  time: number;
257
292
  /** See {@link BurstParameters.count}. */
258
- count: FunctionJSON | number;
293
+ count: ValueGeneratorJSON | number;
259
294
  /** See {@link BurstParameters.cycle}. */
260
295
  cycle: number;
261
296
  /** See {@link BurstParameters.interval}. */
262
297
  interval: number;
263
298
  /** See {@link BurstParameters.probability}. */
264
299
  probability: number;
300
+ /** `cycle`, as files before it was named wrote it; read when there is no `cycle`. */
301
+ cycleCount?: number;
265
302
  }
266
303
 
267
304
  /**
@@ -288,23 +325,23 @@ export interface ParticleSystemJSONParameters {
288
325
  /** See {@link ParticleSystem.duration}. */
289
326
  duration?: number;
290
327
  /** See {@link ParticleSystem.startDelay}. */
291
- startDelay?: FunctionJSON;
328
+ startDelay?: ValueGeneratorJSON;
292
329
  /** The emitter shape, as its `toJSON` writes it. */
293
- shape?: any;
330
+ shape?: ShapeJSON;
294
331
  /** See {@link ParticleSystem.startLife}. */
295
- startLife?: FunctionJSON;
332
+ startLife?: ValueGeneratorJSON;
296
333
  /** See {@link ParticleSystem.startSpeed}. */
297
- startSpeed?: FunctionJSON;
334
+ startSpeed?: ValueGeneratorJSON;
298
335
  /** See {@link ParticleSystem.startRotation}. */
299
- startRotation?: FunctionJSON;
336
+ startRotation?: ValueGeneratorJSON | RotationGeneratorJSON;
300
337
  /** See {@link ParticleSystem.startSize}. */
301
- startSize?: FunctionJSON;
338
+ startSize?: ValueGeneratorJSON | Vector3GeneratorJSON;
302
339
  /** See {@link ParticleSystem.startColor}. */
303
- startColor?: FunctionJSON;
340
+ startColor?: ColorGeneratorJSON;
304
341
  /** See {@link ParticleSystem.emissionOverTime}. */
305
- emissionOverTime?: FunctionJSON;
342
+ emissionOverTime?: ValueGeneratorJSON;
306
343
  /** See {@link ParticleSystem.emissionOverDistance}. */
307
- emissionOverDistance?: FunctionJSON;
344
+ emissionOverDistance?: ValueGeneratorJSON;
308
345
  /** See {@link ParticleSystem.emissionBursts}. */
309
346
  emissionBursts?: Array<BurstParametersJSON>;
310
347
  /** See {@link ParticleSystem.maxParticles}. */
@@ -312,12 +349,12 @@ export interface ParticleSystemJSONParameters {
312
349
  /** See {@link ParticleSystem.onlyUsedByOther}. */
313
350
  onlyUsedByOther?: boolean;
314
351
  /**
315
- * Trail, stretched or mesh settings; a trail's `startLength` as `FunctionJSON`. Left out when
352
+ * Trail, stretched or mesh settings; a trail's `startLength` as `ValueGeneratorJSON`. Left out when
316
353
  * the render mode has none.
317
354
  */
318
- rendererEmitterSettings?: RendererEmitterSettings;
319
- /** Uuid of the mesh particles' geometry entry. */
320
- instancingGeometry?: any;
355
+ rendererEmitterSettings?: RendererEmitterSettingsJSON;
356
+ /** Uuid of the mesh particles' geometry entry; a document made in code may hold the entry itself. */
357
+ instancingGeometry?: string | GeometryEntry;
321
358
  /** A {@link RenderMode} value. */
322
359
  renderMode?: number;
323
360
  /** See {@link ParticleSystem.renderOrder}. */
@@ -336,6 +373,8 @@ export interface ParticleSystemJSONParameters {
336
373
  seed?: number;
337
374
  /** Written only when not `'none'`. */
338
375
  sortMode?: SortMode;
376
+ /** Written only when not `'both'`. */
377
+ renderFace?: RenderFace;
339
378
  /** Written only when true. */
340
379
  alignToDirection?: boolean;
341
380
  /** Written only when not 0. */
@@ -351,7 +390,7 @@ export interface ParticleSystemJSONParameters {
351
390
  /** The camera layer mask. */
352
391
  layers?: number;
353
392
  /** See {@link ParticleSystem.startTileIndex}. */
354
- startTileIndex?: FunctionJSON | number;
393
+ startTileIndex?: ValueGeneratorJSON | number;
355
394
  /** See {@link ParticleSystem.uTileCount}. */
356
395
  uTileCount?: number;
357
396
  /** See {@link ParticleSystem.vTileCount}. */
@@ -375,7 +414,7 @@ export interface ParticleSystemJSONParameters {
375
414
  /** See {@link ParticleSystemParameters.alphaTest}. */
376
415
  alphaTest?: number;
377
416
  /** Each behavior as its `toJSON` writes it. */
378
- behaviors?: Array<any>;
417
+ behaviors?: BehaviorJSON[];
379
418
  /** See {@link ParticleSystem.worldSpace}. */
380
419
  worldSpace?: boolean;
381
420
  /** `[x, y, z]`; written only when non-zero. */
@@ -392,19 +431,19 @@ export interface ParticleSystemJSONParameters {
392
431
  */
393
432
  export interface BabylonMetaData {
394
433
  /** Textures by uuid; null for one that could not be made. */
395
- textures: {[uuid: string]: Texture | null};
434
+ textures: {[uuid: string]: BaseTexture | null};
396
435
  /** Material entries by uuid. */
397
- materials: {[uuid: string]: any};
436
+ materials: {[uuid: string]: QuarksMaterialJSON};
398
437
  /** Geometry entries by uuid. */
399
- geometries: {[uuid: string]: any};
400
- /** Image entries by uuid. */
401
- images?: {[uuid: string]: any};
438
+ geometries: {[uuid: string]: GeometryEntry};
439
+ /** Not used by babylon.quarks; kept for three.js-shaped documents. */
440
+ images?: {[uuid: string]: unknown};
441
+ /** Not used by babylon.quarks; kept for three.js-shaped documents. */
442
+ shapes?: {[uuid: string]: unknown};
402
443
  /** Not used by babylon.quarks; kept for three.js-shaped documents. */
403
- shapes?: {[uuid: string]: any};
444
+ skeletons?: {[uuid: string]: unknown};
404
445
  /** Not used by babylon.quarks; kept for three.js-shaped documents. */
405
- skeletons?: {[uuid: string]: any};
446
+ animations?: {[uuid: string]: unknown};
406
447
  /** Not used by babylon.quarks; kept for three.js-shaped documents. */
407
- animations?: {[uuid: string]: any};
408
- /** Nodes by uuid, which a mesh surface shape links to. */
409
- nodes?: {[uuid: string]: any};
448
+ nodes?: {[uuid: string]: unknown};
410
449
  }