babylon.quarks 0.25.0 → 0.27.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 (79) hide show
  1. package/CHANGELOG.md +854 -8
  2. package/README.md +10 -8
  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 +6683 -0
  10. package/dist/types/index.d.ts +2210 -613
  11. package/package.json +12 -6
  12. package/src/BatchedRenderer.ts +215 -106
  13. package/src/EffectPool.ts +4 -1
  14. package/src/MeshSurfaceEmitter.ts +29 -4
  15. package/src/ParticleEmitter.ts +45 -15
  16. package/src/ParticleSystem.ts +1012 -1962
  17. package/src/QuarksLoader.ts +463 -75
  18. package/src/QuarksPrefab.ts +91 -4
  19. package/src/QuarksSerializer.ts +484 -0
  20. package/src/QuarksUtil.ts +15 -0
  21. package/src/SpriteBatch.ts +149 -238
  22. package/src/TrailBatch.ts +586 -286
  23. package/src/VFXBatch.ts +517 -153
  24. package/src/batchKey.ts +237 -0
  25. package/src/effectResources.ts +79 -0
  26. package/src/envAtlas.ts +119 -17
  27. package/src/index.ts +51 -35
  28. package/src/nodeId.ts +13 -0
  29. package/src/shaderProgram.ts +325 -0
  30. package/src/shaderVariants.ts +225 -0
  31. package/src/shaders/chunks/fog.ts +4 -2
  32. package/src/shaders/chunks/sheet.ts +111 -0
  33. package/src/shaders/chunks/soft.ts +84 -0
  34. package/src/shaders/chunks/surface.ts +132 -42
  35. package/src/shaders/local_particle_physics_vert.glsl.ts +28 -27
  36. package/src/shaders/local_particle_physics_vert.wgsl.ts +30 -28
  37. package/src/shaders/particle_frag.glsl.ts +6 -22
  38. package/src/shaders/particle_frag.wgsl.ts +6 -23
  39. package/src/shaders/particle_physics_frag.glsl.ts +22 -29
  40. package/src/shaders/particle_physics_frag.wgsl.ts +22 -30
  41. package/src/shaders/particle_vert.glsl.ts +11 -26
  42. package/src/shaders/particle_vert.wgsl.ts +11 -25
  43. package/src/shaders/stretched_bb_particle_vert.glsl.ts +21 -39
  44. package/src/shaders/stretched_bb_particle_vert.wgsl.ts +22 -40
  45. package/src/shaders/trail_frag.glsl.ts +14 -1
  46. package/src/shaders/trail_frag.wgsl.ts +14 -0
  47. package/src/shaders/trail_vert.glsl.ts +53 -36
  48. package/src/shaders/trail_vert.wgsl.ts +54 -40
  49. package/src/softDepth.ts +136 -0
  50. package/src/system/BehaviorPlan.ts +85 -0
  51. package/src/system/BoundsTracker.ts +181 -0
  52. package/src/system/EmissionController.ts +360 -0
  53. package/src/system/EventDispatcher.ts +44 -0
  54. package/src/system/ParticleSpawner.ts +274 -0
  55. package/src/system/SimulationClock.ts +130 -0
  56. package/src/system/geometry.ts +111 -0
  57. package/src/system/integrate.ts +428 -0
  58. package/src/system/materialResolve.ts +116 -0
  59. package/src/system/normalize.ts +95 -0
  60. package/src/system/serialization.ts +401 -0
  61. package/src/system/settingsSchema.ts +317 -0
  62. package/src/system/types.ts +410 -0
  63. package/src/textureSource.ts +30 -0
  64. package/src/trailSettings.ts +51 -0
  65. package/src/BatchedParticleRenderer.ts +0 -3
  66. package/src/materials/ParticleMaterials.ts +0 -23
  67. package/src/materials/index.ts +0 -1
  68. package/src/shaders/chunks/index.ts +0 -1
  69. package/src/shaders/chunks/register-shader-chunks.ts +0 -32
  70. package/src/shaders/chunks/soft_fragment.glsl.ts +0 -13
  71. package/src/shaders/chunks/soft_pars_fragment.glsl.ts +0 -20
  72. package/src/shaders/chunks/soft_pars_vertex.glsl.ts +0 -6
  73. package/src/shaders/chunks/soft_vertex.glsl.ts +0 -6
  74. package/src/shaders/chunks/tile_fragment.glsl.ts +0 -9
  75. package/src/shaders/chunks/tile_pars_fragment.glsl.ts +0 -9
  76. package/src/shaders/chunks/tile_pars_vertex.glsl.ts +0 -28
  77. package/src/shaders/chunks/tile_vertex.glsl.ts +0 -18
  78. package/src/shaders/index.ts +0 -9
  79. package/src/shaders/local_particle_vert.glsl.ts +0 -50
package/src/VFXBatch.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  import {BoundingInfo} from '@babylonjs/core/Culling/boundingInfo';
2
- import {Constants} from '@babylonjs/core/Engines/constants';
3
2
  import {BaseTexture} from '@babylonjs/core/Materials/Textures/baseTexture';
3
+ import type {Effect} from '@babylonjs/core/Materials/effect';
4
4
  import {ShaderMaterial} from '@babylonjs/core/Materials/shaderMaterial';
5
5
  import {Color3} from '@babylonjs/core/Maths/math.color';
6
6
  import {Vector2 as BVector2, Vector3 as BVector3, Vector4 as BVector4} from '@babylonjs/core/Maths/math.vector';
@@ -9,6 +9,8 @@ import {Scene} from '@babylonjs/core/scene';
9
9
  import {IParticleSystem, Vector3} from 'quarks.core';
10
10
  import {VFXBatchSettings} from './BatchedRenderer';
11
11
  import type {ParticleSystem} from './ParticleSystem';
12
+ import {storedBatchSettings} from './batchKey';
13
+ import {DepthBinding, softDepthParams} from './softDepth';
12
14
 
13
15
  /**
14
16
  * The order the particles of a batch are drawn in (Unity's Sort Mode). Alpha
@@ -23,12 +25,25 @@ import type {ParticleSystem} from './ParticleSystem';
23
25
  */
24
26
  export type SortMode = 'none' | 'distance' | 'oldestInFront' | 'youngestInFront';
25
27
 
28
+ /** How a system draws its particles (Unity's Render Mode), see {@link ParticleSystem.renderMode}. */
26
29
  export enum RenderMode {
30
+ /** A quad facing the camera (Unity's Billboard). The default. */
27
31
  BillBoard = 0,
32
+ /**
33
+ * A quad facing the camera and stretched along the particle's velocity (Unity's Stretched
34
+ * Billboard), by `speedFactor` and `lengthFactor` in the system's `rendererEmitterSettings`.
35
+ */
28
36
  StretchedBillBoard = 1,
37
+ /** The system's mesh, one instance per particle (Unity's Mesh), see {@link ParticleSystem.instancingGeometry}. */
29
38
  Mesh = 2,
39
+ /**
40
+ * A ribbon drawn through the particle's past positions (Unity's Trails module, Niagara's
41
+ * ribbon), facing the camera; its settings are `TrailSettings` in `rendererEmitterSettings`.
42
+ */
30
43
  Trail = 3,
44
+ /** A quad lying flat in the world's XZ plane, facing up (Unity's Horizontal Billboard). */
31
45
  HorizontalBillBoard = 4,
46
+ /** A quad standing upright and turning only about the world's Y axis to face the camera (Unity's Vertical Billboard). */
32
47
  VerticalBillBoard = 5,
33
48
  /**
34
49
  * A quad laid from each particle to the system's {@link ParticleSystem.beamEnd}
@@ -87,9 +102,12 @@ export interface MeshRim {
87
102
  strength: number;
88
103
  /**
89
104
  * `'add'` (default) adds the colour, for a glowing edge; `'mix'` blends the
90
- * surface toward it, for an outline (a dark ink line with a dark colour).
105
+ * surface toward it, for an outline (a dark ink line with a dark colour);
106
+ * `'fade'` leaves the colour and fades the faces turned to the camera by
107
+ * `strength` instead, alpha times `1 - strength * (1 - edge)`: a wall of
108
+ * light that reads thick along its silhouette and thin across its face.
91
109
  */
92
- mode: 'add' | 'mix';
110
+ mode: 'add' | 'mix' | 'fade';
93
111
  }
94
112
 
95
113
  /**
@@ -107,7 +125,7 @@ export function normalizeMeshRim(rim: Partial<MeshRim> | null | undefined): Mesh
107
125
  color: [channel(color[0], 1), channel(color[1], 1), channel(color[2], 1)],
108
126
  power: Math.max(0.01, channel(rim.power, 2)),
109
127
  strength: Math.max(0, channel(rim.strength, 1)),
110
- mode: rim.mode === 'mix' ? 'mix' : 'add',
128
+ mode: rim.mode === 'mix' || rim.mode === 'fade' ? rim.mode : 'add',
111
129
  };
112
130
  }
113
131
 
@@ -129,6 +147,145 @@ export function meshRimEqual(a: MeshRim | undefined, b: MeshRim | undefined): bo
129
147
  );
130
148
  }
131
149
 
150
+ /**
151
+ * Which texture coordinates a texture is sampled with, as Unity's UV channels
152
+ * and Niagara's ribbon UV0 / UV1:
153
+ *
154
+ * - `0`: the particle's first UV, with the sprite sheet's cell applied: on a
155
+ * sheet the current frame's cell, on a trail the cell's stretch along it, on
156
+ * a mesh its first UV set. Read before the map's own layout and distortion,
157
+ * so a texture on it does not move with them.
158
+ * - `1`: the particle's own UV, never moved by the sheet. The whole quad on a
159
+ * billboard, a stretched billboard or a beam (mirrored with the quad when it
160
+ * flips); on a trail U along it the way the map's runs (from the tail, or
161
+ * from the head with `uvFromHead`) and V across it; on a mesh its second UV
162
+ * set (`instancingUVs2`), or its first when it has none.
163
+ *
164
+ * A mesh can then repeat its picture along UV0 while a mask on UV1 runs once
165
+ * across its whole length, as a lightmap UV does in a game engine.
166
+ */
167
+ export type UVChannel = 0 | 1;
168
+
169
+ /**
170
+ * How a texture is laid over the particle: its UV channel, then tiling and
171
+ * offset as in Unity's material Tiling / Offset, then a scroll on the
172
+ * renderer's clock as Unreal's Panner. Sampled at
173
+ * `uv(channel) * tiling + offset + scroll * time`; the offset is used as
174
+ * given, so a clamped texture can be moved off its edge, and only the
175
+ * scroll's share is wrapped (over two repeats, so a mirrored texture stays
176
+ * seamless). A texture that scrolls or tiles should repeat.
177
+ */
178
+ export interface TextureUV {
179
+ /** See {@link UVChannel}. Default 0. */
180
+ channel: UVChannel;
181
+ /** Repeats across the channel's 0…1, `[u, v]`. Default `[1, 1]`. */
182
+ tiling: [number, number];
183
+ /** Added after tiling, in texture UV units. Default `[0, 0]`. */
184
+ offset: [number, number];
185
+ /** UV units per second on the renderer's clock. Default `[0, 0]`. */
186
+ scroll: [number, number];
187
+ }
188
+
189
+ /** A complete {@link TextureUV} from a partial one, with `defaults` under the fields left out. */
190
+ export function normalizeTextureUV(
191
+ uv: Partial<TextureUV> | null | undefined,
192
+ defaults: Partial<TextureUV> = {}
193
+ ): TextureUV {
194
+ const source = uv && typeof uv === 'object' ? uv : {};
195
+ const num = (value: unknown, fallback: number) =>
196
+ typeof value === 'number' && Number.isFinite(value) ? value : fallback;
197
+ const pair = (value: unknown, fallback: [number, number]): [number, number] =>
198
+ Array.isArray(value) ? [num(value[0], fallback[0]), num(value[1], fallback[1])] : [fallback[0], fallback[1]];
199
+ const channel = source.channel ?? defaults.channel;
200
+ return {
201
+ channel: channel === 1 ? 1 : 0,
202
+ tiling: pair(source.tiling, defaults.tiling ?? [1, 1]),
203
+ offset: pair(source.offset, defaults.offset ?? [0, 0]),
204
+ scroll: pair(source.scroll, defaults.scroll ?? [0, 0]),
205
+ };
206
+ }
207
+
208
+ /** True when both lay a texture the same way. */
209
+ export function textureUVEqual(a: TextureUV, b: TextureUV): boolean {
210
+ return (
211
+ a === b ||
212
+ (a.channel === b.channel &&
213
+ a.tiling[0] === b.tiling[0] &&
214
+ a.tiling[1] === b.tiling[1] &&
215
+ a.offset[0] === b.offset[0] &&
216
+ a.offset[1] === b.offset[1] &&
217
+ a.scroll[0] === b.scroll[0] &&
218
+ a.scroll[1] === b.scroll[1])
219
+ );
220
+ }
221
+
222
+ /** The fields of `uv` that differ from `defaults`, for JSON written only when set. */
223
+ export function textureUVToJSON(uv: TextureUV, defaults: Partial<TextureUV> = {}): Partial<TextureUV> | undefined {
224
+ const base = normalizeTextureUV(undefined, defaults);
225
+ const json: Partial<TextureUV> = {};
226
+ if (uv.channel !== base.channel) json.channel = uv.channel;
227
+ if (uv.tiling[0] !== base.tiling[0] || uv.tiling[1] !== base.tiling[1]) json.tiling = [...uv.tiling];
228
+ if (uv.offset[0] !== base.offset[0] || uv.offset[1] !== base.offset[1]) json.offset = [...uv.offset];
229
+ if (uv.scroll[0] !== base.scroll[0] || uv.scroll[1] !== base.scroll[1]) json.scroll = [...uv.scroll];
230
+ return Object.keys(json).length > 0 ? json : undefined;
231
+ }
232
+
233
+ /**
234
+ * Which value of the map's texel picks the colour along a gradient map's
235
+ * ramp: one channel, so a texture can pack several masks (Unreal's and
236
+ * Unity's channel-packed textures), or the Rec. 709 luminance of its colour.
237
+ */
238
+ export type GradientMapChannel = 'r' | 'g' | 'b' | 'a' | 'luminance';
239
+
240
+ /**
241
+ * A gradient map (colour ramp): the map is recoloured by looking its
242
+ * {@link channel} up along U of {@link ramp}. Without a map the lookup reads
243
+ * white, so the ramp's right-hand end tints every particle, in every render
244
+ * mode alike. See {@link VFXBatchSettings.gradientMap}.
245
+ */
246
+ export interface ParticleGradientMap {
247
+ /**
248
+ * The ramp, read along U at mid V: low values on the left. Make it without
249
+ * mipmaps and clamped. Required: without one there is no gradient map.
250
+ */
251
+ ramp: BaseTexture | null;
252
+ /**
253
+ * The texel value the ramp is looked up by. Default `'r'`. With `'a'` the
254
+ * ramp's alpha becomes the texel's alpha, so a soft shape can glow at its
255
+ * fringe and stay solid at its heart; with any other channel the ramp's
256
+ * alpha multiplies the map's.
257
+ */
258
+ channel: GradientMapChannel;
259
+ }
260
+
261
+ /** The weights a texel is dotted with to look up the ramp, per {@link GradientMapChannel}. */
262
+ export const GRADIENT_MAP_WEIGHTS: Readonly<Record<GradientMapChannel, readonly [number, number, number, number]>> = {
263
+ r: [1, 0, 0, 0],
264
+ g: [0, 1, 0, 0],
265
+ b: [0, 0, 1, 0],
266
+ a: [0, 0, 0, 1],
267
+ luminance: [0.2126, 0.7152, 0.0722, 0],
268
+ };
269
+
270
+ /** A complete gradient map, or undefined when unset or without a ramp. */
271
+ export function normalizeGradientMap(
272
+ gradientMap: Partial<ParticleGradientMap> | null | undefined
273
+ ): ParticleGradientMap | undefined {
274
+ if (!gradientMap || typeof gradientMap !== 'object' || !gradientMap.ramp) {
275
+ return undefined;
276
+ }
277
+ const channel = gradientMap.channel;
278
+ return {
279
+ ramp: gradientMap.ramp,
280
+ channel: channel !== undefined && channel in GRADIENT_MAP_WEIGHTS ? channel : 'r',
281
+ };
282
+ }
283
+
284
+ /** True when both recolour the same way (or both not at all). */
285
+ export function gradientMapEqual(a: ParticleGradientMap | undefined, b: ParticleGradientMap | undefined): boolean {
286
+ return a === b || (!!a && !!b && a.ramp === b.ramp && a.channel === b.channel);
287
+ }
288
+
132
289
  /**
133
290
  * Alpha erosion: the particle's alpha decides how much of its shape is left
134
291
  * instead of how transparent it is, so `ColorOverLife` fading alpha out
@@ -137,41 +294,51 @@ export function meshRimEqual(a: MeshRim | undefined, b: MeshRim | undefined): bo
137
294
  */
138
295
  export interface ParticleErosion {
139
296
  /**
140
- * Mask whose red channel orders what goes first, sampled with the
141
- * particle's UV (the quad, mesh or trail UV, unscrolled). Null: the map's
142
- * own alpha, so a soft shape shrinks from its edges.
297
+ * Mask whose red channel orders what goes first, laid by {@link maskUV}.
298
+ * Null: the map's own alpha, so a soft shape shrinks from its edges.
143
299
  */
144
300
  mask: BaseTexture | null;
301
+ /**
302
+ * How the mask is laid over the particle, see {@link TextureUV}. On UV
303
+ * channel 1 a gradient mask wipes a whole trail or mesh however its map
304
+ * repeats. Default channel 0, untiled, still.
305
+ */
306
+ maskUV: TextureUV;
145
307
  /** Width of the soft transition at the cut, in mask units. Default 0.05. */
146
308
  softness: number;
147
309
  /** Width of the glowing band above the cut, in mask units; 0 (default) draws none. */
148
310
  edgeWidth: number;
149
311
  /** Colour added in the edge band, linear RGB, may exceed 1. Default `[1, 1, 1]`. */
150
312
  edgeColor: [number, number, number];
151
- /** Mask UV scroll, UV units per second. Default `[0, 0]`. */
152
- maskScroll: [number, number];
153
313
  }
154
- // Limits worth knowing: the mask is sampled with the UV as drawn (on a sheet,
155
- // the current tile's), erosion only changes alpha (one-one and premultiplied
314
+ // Limits worth knowing: erosion only changes alpha (one-one and premultiplied
156
315
  // blending show a hard cut), and with neither a mask nor a map there is
157
316
  // nothing to order the cut by, so the shape goes all at once near alpha 0.
158
317
  // Scrolling runs on the renderer's clock, not a system's playback speed.
159
318
 
319
+ /** {@link ParticleErosion} as given: any field may be left out, the mask's layout too. */
320
+ export type ParticleErosionOptions = Partial<Omit<ParticleErosion, 'maskUV'>> & {
321
+ /** See {@link ParticleErosion.maskUV}; fields left out take their defaults. */
322
+ maskUV?: Partial<TextureUV>;
323
+ };
324
+
325
+ /** The width of the soft band an erosion cut fades over when `softness` is left out, in mask units. */
326
+ export const EROSION_DEFAULT_SOFTNESS = 0.05;
327
+
160
328
  /** A complete erosion from a partial one, every field defaulted, or undefined when `erosion` is unset. */
161
- export function normalizeErosion(erosion: Partial<ParticleErosion> | null | undefined): ParticleErosion | undefined {
329
+ export function normalizeErosion(erosion: ParticleErosionOptions | null | undefined): ParticleErosion | undefined {
162
330
  if (!erosion || typeof erosion !== 'object') {
163
331
  return undefined;
164
332
  }
165
333
  const num = (value: unknown, fallback: number) =>
166
334
  typeof value === 'number' && Number.isFinite(value) ? value : fallback;
167
335
  const color = Array.isArray(erosion.edgeColor) ? erosion.edgeColor : [1, 1, 1];
168
- const scroll = Array.isArray(erosion.maskScroll) ? erosion.maskScroll : [0, 0];
169
336
  return {
170
337
  mask: erosion.mask ?? null,
171
- softness: Math.max(0.0001, num(erosion.softness, 0.05)),
338
+ maskUV: normalizeTextureUV(erosion.maskUV),
339
+ softness: Math.max(0.0001, num(erosion.softness, EROSION_DEFAULT_SOFTNESS)),
172
340
  edgeWidth: Math.max(0, num(erosion.edgeWidth, 0)),
173
341
  edgeColor: [num(color[0], 1), num(color[1], 1), num(color[2], 1)],
174
- maskScroll: [num(scroll[0], 0), num(scroll[1], 0)],
175
342
  };
176
343
  }
177
344
 
@@ -185,54 +352,161 @@ export function erosionEqual(a: ParticleErosion | undefined, b: ParticleErosion
185
352
  }
186
353
  return (
187
354
  a.mask === b.mask &&
355
+ textureUVEqual(a.maskUV, b.maskUV) &&
188
356
  a.softness === b.softness &&
189
357
  a.edgeWidth === b.edgeWidth &&
190
358
  a.edgeColor[0] === b.edgeColor[0] &&
191
359
  a.edgeColor[1] === b.edgeColor[1] &&
192
- a.edgeColor[2] === b.edgeColor[2] &&
193
- a.maskScroll[0] === b.maskScroll[0] &&
194
- a.maskScroll[1] === b.maskScroll[1]
360
+ a.edgeColor[2] === b.edgeColor[2]
195
361
  );
196
362
  }
197
363
 
198
- /** A UV scroll speed, or undefined when unset or zero. */
199
- export function normalizeUVScroll(scroll: ArrayLike<number> | null | undefined): [number, number] | undefined {
200
- if (!scroll || typeof scroll !== 'object') {
364
+ /** The distortion noise's default layout: drifting up half a repeat a second. */
365
+ export const UV_DISTORTION_DEFAULT_UV: Partial<TextureUV> = {scroll: [0, 0.5]};
366
+
367
+ /**
368
+ * UV distortion: the map's UV pushed about by a noise texture, for heat
369
+ * shimmer, licking flames and breathing auras. See
370
+ * {@link VFXBatchSettings.uvDistortion}.
371
+ */
372
+ export interface ParticleUVDistortion {
373
+ /**
374
+ * Noise whose red and green channels, around 0.5, push U and V. It should
375
+ * wrap (repeat). Required: without one there is no distortion.
376
+ */
377
+ map: BaseTexture | null;
378
+ /**
379
+ * How the noise is laid over the particle, see {@link TextureUV}. Default
380
+ * channel 0, untiled, scrolling `[0, 0.5]`.
381
+ */
382
+ mapUV: TextureUV;
383
+ /** How far the map's UV is pushed at most, in UV units. Default 0.05. */
384
+ strength: number;
385
+ }
386
+
387
+ /** {@link ParticleUVDistortion} as given: any field may be left out, the noise's layout too. */
388
+ export type ParticleUVDistortionOptions = Partial<Omit<ParticleUVDistortion, 'mapUV'>> & {
389
+ /** See {@link ParticleUVDistortion.mapUV}; fields left out take their defaults. */
390
+ mapUV?: Partial<TextureUV>;
391
+ };
392
+
393
+ /** How far a distortion pushes the map's UV at most when `strength` is left out, in UV units. */
394
+ export const UV_DISTORTION_DEFAULT_STRENGTH = 0.05;
395
+
396
+ /** A complete distortion from a partial one, or undefined when unset or without a map. */
397
+ export function normalizeUVDistortion(
398
+ distortion: ParticleUVDistortionOptions | null | undefined
399
+ ): ParticleUVDistortion | undefined {
400
+ if (!distortion || typeof distortion !== 'object' || !distortion.map) {
401
+ return undefined;
402
+ }
403
+ const strength = distortion.strength;
404
+ return {
405
+ map: distortion.map,
406
+ mapUV: normalizeTextureUV(distortion.mapUV, UV_DISTORTION_DEFAULT_UV),
407
+ strength: Math.max(
408
+ 0,
409
+ typeof strength === 'number' && Number.isFinite(strength) ? strength : UV_DISTORTION_DEFAULT_STRENGTH
410
+ ),
411
+ };
412
+ }
413
+
414
+ /** True when both describe the same distortion (or both none). */
415
+ export function uvDistortionEqual(a: ParticleUVDistortion | undefined, b: ParticleUVDistortion | undefined): boolean {
416
+ if (a === b) {
417
+ return true;
418
+ }
419
+ if (!a || !b) {
420
+ return false;
421
+ }
422
+ return a.map === b.map && a.strength === b.strength && textureUVEqual(a.mapUV, b.mapUV);
423
+ }
424
+
425
+ /**
426
+ * The main map's layout, or undefined when it is the identity (channel 0,
427
+ * untiled, unmoved, still), which costs nothing in the shader.
428
+ */
429
+ export function normalizeMapUV(uv: Partial<TextureUV> | null | undefined): TextureUV | undefined {
430
+ if (!uv || typeof uv !== 'object') {
201
431
  return undefined;
202
432
  }
203
- const u = Number.isFinite(scroll[0]) ? scroll[0] : 0;
204
- const v = Number.isFinite(scroll[1]) ? scroll[1] : 0;
205
- return u === 0 && v === 0 ? undefined : [u, v];
433
+ const normalized = normalizeTextureUV(uv);
434
+ return textureUVToJSON(normalized) === undefined ? undefined : normalized;
206
435
  }
207
436
 
208
- /** True when both scroll the same way (or both not at all). */
209
- export function uvScrollEqual(a: [number, number] | undefined, b: [number, number] | undefined): boolean {
210
- return a === b || (!!a && !!b && a[0] === b[0] && a[1] === b[1]);
437
+ /** True when both lay the map the same way (or both as drawn). */
438
+ export function mapUVEqual(a: TextureUV | undefined, b: TextureUV | undefined): boolean {
439
+ return a === b || (!!a && !!b && textureUVEqual(a, b));
211
440
  }
212
441
 
442
+ /**
443
+ * True when a texture effect of `settings` samples UV channel 1, see
444
+ * {@link UVChannel}: the map, the distortion noise or an erosion mask laid
445
+ * on it.
446
+ */
447
+ export function readsUV1(settings: {
448
+ mapUV?: TextureUV;
449
+ uvDistortion?: ParticleUVDistortion;
450
+ erosion?: ParticleErosion;
451
+ }): boolean {
452
+ const {mapUV, uvDistortion, erosion} = settings;
453
+ return (
454
+ mapUV?.channel === 1 || uvDistortion?.mapUV.channel === 1 || (!!erosion?.mask && erosion.maskUV.channel === 1)
455
+ );
456
+ }
457
+
458
+ /**
459
+ * A batch's copy of the {@link VFXBatchSettings} its systems share: every optional field
460
+ * defaulted, and every field the batch's render mode ignores at its default (an opaque batch's
461
+ * `materialBlendMode` reads `ALPHA_DISABLE`, a billboard batch's `rim` is unset). The renderer
462
+ * adds a system to the batch whose settings match its own.
463
+ */
213
464
  export interface StoredBatchSettings {
465
+ /** See {@link VFXBatchSettings.instancingGeometry}. */
214
466
  instancingGeometry: Float32Array;
467
+ /** See {@link VFXBatchSettings.instancingIndices}. */
215
468
  instancingIndices: Uint32Array | Uint16Array;
469
+ /** See {@link VFXBatchSettings.instancingUVs}. */
216
470
  instancingUVs?: Float32Array;
471
+ /** The mesh's second UV set, see {@link UVChannel}. */
472
+ instancingUVs2?: Float32Array;
473
+ /** See {@link VFXBatchSettings.instancingNormals}. */
217
474
  instancingNormals?: Float32Array;
475
+ /** See {@link VFXBatchSettings.renderMode}. */
218
476
  renderMode: RenderMode;
477
+ /** See {@link VFXBatchSettings.renderOrder}. */
219
478
  renderOrder: number;
479
+ /** See {@link VFXBatchSettings.uTileCount}. */
220
480
  uTileCount: number;
481
+ /** See {@link VFXBatchSettings.vTileCount}. */
221
482
  vTileCount: number;
483
+ /** See {@link VFXBatchSettings.blendTiles}. */
222
484
  blendTiles: boolean;
485
+ /** See {@link VFXBatchSettings.softParticles}. */
223
486
  softParticles: boolean;
487
+ /** See {@link VFXBatchSettings.softNearFade}. */
224
488
  softNearFade: number;
489
+ /** See {@link VFXBatchSettings.softFarFade}. */
225
490
  softFarFade: number;
491
+ /** See {@link VFXBatchSettings.materialBlendMode}. */
226
492
  materialBlendMode: number;
493
+ /** See {@link VFXBatchSettings.materialTransparent}. */
227
494
  materialTransparent: boolean;
495
+ /** See {@link VFXBatchSettings.materialDepthTest}. */
228
496
  materialDepthTest: boolean;
497
+ /** See {@link VFXBatchSettings.materialDepthWrite}. */
229
498
  materialDepthWrite: boolean;
499
+ /** See {@link VFXBatchSettings.materialAlphaTest}. */
230
500
  materialAlphaTest: number;
231
- texture: any;
501
+ /** See {@link VFXBatchSettings.texture}. */
502
+ texture: BaseTexture | null;
503
+ /** See {@link VFXBatchSettings.reflectionTexture}. */
232
504
  reflectionTexture: BaseTexture | null;
505
+ /** See {@link VFXBatchSettings.reflectionLevel}. */
233
506
  reflectionLevel: number;
234
- reflectionFaces: BaseTexture[] | null;
507
+ /** See {@link VFXBatchSettings.reflectionAtlas}. */
235
508
  reflectionAtlas: BaseTexture | null;
509
+ /** See {@link VFXBatchSettings.layerMask}. */
236
510
  layerMask: number;
237
511
  /** Babylon rendering group the batch mesh draws in, see {@link VFXBatchSettings.renderingGroupId}. */
238
512
  renderingGroupId: number;
@@ -250,62 +524,48 @@ export interface StoredBatchSettings {
250
524
  lighting?: MeshLighting;
251
525
  /** Mesh particle rim, see {@link VFXBatchSettings.rim}. */
252
526
  rim?: MeshRim;
253
- /** Map UV scroll, see {@link VFXBatchSettings.uvScroll}. */
254
- uvScroll?: [number, number];
527
+ /** The main map's layout, see {@link VFXBatchSettings.mapUV}. */
528
+ mapUV?: TextureUV;
529
+ /** Map UV distortion, see {@link VFXBatchSettings.uvDistortion}. */
530
+ uvDistortion?: ParticleUVDistortion;
255
531
  /** Colour ramp for the map, see {@link VFXBatchSettings.gradientMap}. */
256
- gradientMap: BaseTexture | null;
532
+ gradientMap?: ParticleGradientMap;
257
533
  /** Alpha erosion, see {@link VFXBatchSettings.erosion}. */
258
534
  erosion?: ParticleErosion;
259
535
  }
260
536
 
537
+ /**
538
+ * One draw call of a {@link BatchedRenderer}: the systems that share a material, texture and
539
+ * geometry, drawn together from one instanced mesh. The renderer makes and frees batches as
540
+ * systems are added and their settings change; read them through `renderer.batches` to inspect
541
+ * what is drawn, not to change it.
542
+ */
261
543
  export abstract class VFXBatch {
544
+ /** The Babylon mesh the batch draws with. */
262
545
  mesh: Mesh;
546
+ /** The systems drawn by this batch. */
263
547
  systems: Set<IParticleSystem>;
548
+ /** The settings every system in the batch shares, with what the render mode ignores at its default. */
264
549
  settings: StoredBatchSettings;
550
+ /** @internal */
265
551
  protected maxParticles: number;
552
+ /** @internal */
266
553
  protected scene: Scene;
554
+ /** @internal */
267
555
  protected readonly visibleSystems: IParticleSystem[] = [];
556
+ /**
557
+ * The depth texture soft particles read, from {@link BatchedRenderer.setDepthTexture}.
558
+ * @internal
559
+ */
560
+ protected depth: DepthBinding | null;
268
561
 
269
- protected constructor(settings: VFXBatchSettings, scene: Scene) {
562
+ /** @internal */
563
+ protected constructor(settings: VFXBatchSettings, scene: Scene, depth: DepthBinding | null = null) {
270
564
  this.scene = scene;
565
+ this.depth = depth;
271
566
  this.maxParticles = 1000;
272
567
  this.systems = new Set<IParticleSystem>();
273
- this.settings = {
274
- instancingGeometry: settings.instancingGeometry,
275
- instancingIndices: settings.instancingIndices,
276
- instancingUVs: settings.instancingUVs,
277
- instancingNormals: settings.instancingNormals,
278
- renderMode: settings.renderMode,
279
- renderOrder: settings.renderOrder,
280
- uTileCount: settings.uTileCount,
281
- vTileCount: settings.vTileCount,
282
- blendTiles: settings.blendTiles,
283
- softParticles: settings.softParticles,
284
- softNearFade: settings.softNearFade,
285
- softFarFade: settings.softFarFade,
286
- materialBlendMode: settings.materialBlendMode,
287
- materialTransparent: settings.materialTransparent,
288
- materialDepthTest: settings.materialDepthTest,
289
- materialDepthWrite: settings.materialDepthWrite,
290
- materialAlphaTest: settings.materialAlphaTest,
291
- texture: settings.texture,
292
- reflectionTexture: settings.reflectionTexture ?? null,
293
- reflectionLevel: settings.reflectionLevel ?? 1,
294
- reflectionFaces: settings.reflectionFaces ?? null,
295
- reflectionAtlas: settings.reflectionAtlas ?? null,
296
- pivot: settings.pivot?.clone(),
297
- sortMode: settings.sortMode ?? 'none',
298
- minParticleSize: settings.minParticleSize ?? 0,
299
- maxParticleSize: settings.maxParticleSize ?? Infinity,
300
- layerMask: settings.layerMask,
301
- renderingGroupId: settings.renderingGroupId ?? 0,
302
- applyFog: settings.applyFog ?? true,
303
- lighting: settings.lighting,
304
- rim: normalizeMeshRim(settings.rim),
305
- uvScroll: normalizeUVScroll(settings.uvScroll),
306
- gradientMap: settings.gradientMap ?? null,
307
- erosion: normalizeErosion(settings.erosion),
308
- };
568
+ this.settings = storedBatchSettings(settings);
309
569
  this.mesh = new Mesh('vfxBatch', scene);
310
570
  this.configureMesh();
311
571
  }
@@ -333,6 +593,12 @@ export abstract class VFXBatch {
333
593
  mesh.alphaIndex = this.settings.renderOrder;
334
594
  // The shaders fog themselves; Babylon's own fog pass does not apply to a ShaderMaterial.
335
595
  mesh.applyFog = false;
596
+ // Its geometry is one quad or mesh at the origin, not the particles, so a pick or a
597
+ // collision against it would hit nothing that is drawn. The renderer rebuilds batches
598
+ // from its systems, so a scene serializer has nothing to write either.
599
+ mesh.isPickable = false;
600
+ mesh.checkCollisions = false;
601
+ mesh.doNotSerialize = true;
336
602
  }
337
603
 
338
604
  private readonly boundsMin_ = new Vector3();
@@ -395,23 +661,26 @@ export abstract class VFXBatch {
395
661
  }
396
662
 
397
663
  /**
398
- * Fog defines and uniforms for a material of this batch, and the per-bind
399
- * upload of the scene's current fog. Mode 0 (none) when the scene has fog
400
- * disabled, so toggling fog at runtime needs no recompile.
664
+ * Writes a uniform that changes per bind, such as one read off the active camera, to the
665
+ * material for the next bind and to its bound effect for this one: ShaderMaterial uploads its
666
+ * stored values before the bind observers run, so a value written only to the material would
667
+ * reach the GPU one draw late, and with two cameras it would always be the other camera's.
401
668
  */
402
- protected static readonly FOG_UNIFORMS = ['vFogInfos', 'vFogColor'];
403
-
404
- protected fogDefines(): string[] {
405
- if (!this.settings.applyFog) {
406
- return [];
669
+ protected static writeBound(material: ShaderMaterial, write: (target: ShaderMaterial | Effect) => void): void {
670
+ write(material);
671
+ // An effect still compiling has no pipeline context to write to; it gets the stored value
672
+ // when it is first bound. A sub mesh's effect override is not reached here.
673
+ const effect = material.getEffect();
674
+ if (effect?.isReady()) {
675
+ write(effect);
407
676
  }
408
- const additive =
409
- this.settings.materialTransparent &&
410
- (this.settings.materialBlendMode === Constants.ALPHA_ADD ||
411
- this.settings.materialBlendMode === Constants.ALPHA_ONEONE);
412
- return additive ? ['FOG', 'FOG_ADDITIVE'] : ['FOG'];
413
677
  }
414
678
 
679
+ /**
680
+ * The per-bind upload of the scene's current fog. Mode 0 (none) when the scene has fog
681
+ * disabled, so toggling fog at runtime needs no recompile.
682
+ * @internal
683
+ */
415
684
  protected bindFog(material: ShaderMaterial): void {
416
685
  if (!this.settings.applyFog) {
417
686
  return;
@@ -434,90 +703,71 @@ export abstract class VFXBatch {
434
703
  */
435
704
  time = 0;
436
705
 
437
- /** Defines of the texture effects (see `shaders/chunks/surface.ts`) this batch uses. */
438
- protected surfaceDefines(): string[] {
439
- const defines: string[] = [];
440
- if (this.settings.uvScroll) {
441
- defines.push('UV_SCROLL');
442
- }
443
- if (this.settings.gradientMap) {
444
- defines.push('GRADIENT_MAP');
445
- }
446
- const erosion = this.settings.erosion;
447
- if (erosion) {
448
- defines.push('EROSION');
449
- if (erosion.mask) {
450
- defines.push('EROSION_MASK');
451
- }
452
- }
453
- return defines;
454
- }
455
-
456
- /** Adds the uniforms and samplers of the texture effects this batch uses. */
457
- protected surfaceUniforms(uniforms: string[], samplers: string[]): void {
458
- if (this.settings.uvScroll) {
459
- uniforms.push('uvScrollOffset');
460
- }
461
- if (this.settings.gradientMap) {
462
- samplers.push('gradientMap');
463
- uniforms.push('gradientMapRange');
464
- }
465
- const erosion = this.settings.erosion;
466
- if (erosion) {
467
- uniforms.push('erosionParams', 'erosionEdgeColor');
468
- if (erosion.mask) {
469
- samplers.push('erosionMask');
470
- }
471
- }
706
+ /** True when a texture effect of this batch samples UV channel 1, see {@link UVChannel}. */
707
+ protected usesUV1(): boolean {
708
+ return readsUV1(this.settings);
472
709
  }
473
710
 
474
711
  /** Sets the texture effects' textures and constants, and advances their scrolling on every bind. */
475
712
  protected bindSurface(material: ShaderMaterial): void {
476
- const {uvScroll, gradientMap, erosion} = this.settings;
477
- if (gradientMap) {
478
- material.setTexture('gradientMap', gradientMap);
713
+ const {mapUV, uvDistortion, gradientMap, erosion} = this.settings;
714
+ if (uvDistortion?.map) {
715
+ material.setTexture('distortionMap', uvDistortion.map);
479
716
  }
480
- if (erosion) {
481
- if (erosion.mask) {
482
- material.setTexture('erosionMask', erosion.mask);
483
- }
484
- material.setVector3(
485
- 'erosionEdgeColor',
486
- new BVector3(erosion.edgeColor[0], erosion.edgeColor[1], erosion.edgeColor[2])
487
- );
717
+ if (gradientMap?.ramp) {
718
+ material.setTexture('gradientMap', gradientMap.ramp);
719
+ }
720
+ if (erosion?.mask) {
721
+ material.setTexture('erosionMask', erosion.mask);
488
722
  }
489
- if (!uvScroll && !erosion && !gradientMap) {
723
+ this.writeSurfaceParams(material);
724
+ if (!mapUV && !uvDistortion && !erosion?.mask && !gradientMap) {
490
725
  return;
491
726
  }
492
727
  // Wrapped on the CPU: a raw speed × time grows until float32 UVs lose their fraction.
493
- const wrap = (value: number) => value - Math.floor(value);
494
- const scrollOffset = new BVector2();
495
- const erosionParams = new BVector4();
728
+ // Over two repeats, not one: a mirrored texture repeats every two, and
729
+ // wrapped over one it jumped to its mirror image at every wrap.
730
+ const wrap = (value: number) => value - 2 * Math.floor(value / 2);
731
+ const mapTransform = new BVector4();
732
+ const distortionTransform = new BVector4();
733
+ const maskTransform = new BVector4();
496
734
  const gradientRange = new BVector2(0, 1);
735
+ // Only the scroll is wrapped: an offset is kept as given, so a clamped
736
+ // texture moved off its edge stays moved.
737
+ const transform = (uv: TextureUV, time: number, out: BVector4) =>
738
+ out.set(
739
+ uv.tiling[0],
740
+ uv.tiling[1],
741
+ uv.offset[0] + wrap(uv.scroll[0] * time),
742
+ uv.offset[1] + wrap(uv.scroll[1] * time)
743
+ );
497
744
  // Written to the material for the next bind and to the bound effect for
498
745
  // this one: ShaderMaterial uploads its values before the bind observers run.
746
+ // The layouts are read off the settings on every bind, so a live change
747
+ // to a tiling or scroll reaches the next draw.
499
748
  const write = () => {
500
749
  const time = this.time;
501
750
  const effect = material.getEffect();
502
- if (uvScroll) {
503
- scrollOffset.set(wrap(uvScroll[0] * time), wrap(uvScroll[1] * time));
504
- material.setVector2('uvScrollOffset', scrollOffset);
505
- effect?.setVector2('uvScrollOffset', scrollOffset);
751
+ const {mapUV, uvDistortion, gradientMap, erosion} = this.settings;
752
+ if (mapUV) {
753
+ transform(mapUV, time, mapTransform);
754
+ material.setVector4('mapTransform', mapTransform);
755
+ effect?.setVector4('mapTransform', mapTransform);
756
+ }
757
+ if (uvDistortion) {
758
+ transform(uvDistortion.mapUV, time, distortionTransform);
759
+ material.setVector4('distortionTransform', distortionTransform);
760
+ effect?.setVector4('distortionTransform', distortionTransform);
506
761
  }
507
- if (erosion) {
508
- erosionParams.set(
509
- erosion.softness,
510
- erosion.edgeWidth,
511
- wrap(erosion.maskScroll[0] * time),
512
- wrap(erosion.maskScroll[1] * time)
513
- );
514
- material.setVector4('erosionParams', erosionParams);
515
- effect?.setVector4('erosionParams', erosionParams);
762
+ if (erosion?.mask) {
763
+ transform(erosion.maskUV, time, maskTransform);
764
+ material.setVector4('erosionMaskTransform', maskTransform);
765
+ effect?.setVector4('erosionMaskTransform', maskTransform);
516
766
  }
517
- if (gradientMap) {
767
+ if (gradientMap?.ramp) {
518
768
  // Texel centres of the first and last texel, so a repeating
519
769
  // ramp does not blend its two ends at black and white.
520
- const width = gradientMap.getSize().width;
770
+ const width = gradientMap.ramp.getSize().width;
521
771
  const half = width > 0 ? 0.5 / width : 0;
522
772
  gradientRange.set(half, 1 - half);
523
773
  material.setVector2('gradientMapRange', gradientRange);
@@ -528,11 +778,56 @@ export abstract class VFXBatch {
528
778
  material.onBindObservable.add(write);
529
779
  }
530
780
 
531
- addSystem(system: IParticleSystem) {
781
+ /** Writes the texture effects' constants (the uniforms their live parameters set). */
782
+ private writeSurfaceParams(material: ShaderMaterial): void {
783
+ const {uvDistortion, gradientMap, erosion} = this.settings;
784
+ if (uvDistortion?.map) {
785
+ material.setFloat('distortionStrength', uvDistortion.strength);
786
+ }
787
+ if (gradientMap?.ramp) {
788
+ const [r, g, b, a] = GRADIENT_MAP_WEIGHTS[gradientMap.channel];
789
+ material.setVector4('gradientMapChannel', new BVector4(r, g, b, a));
790
+ }
791
+ if (erosion) {
792
+ material.setVector2('erosionParams', new BVector2(erosion.softness, erosion.edgeWidth));
793
+ material.setVector3(
794
+ 'erosionEdgeColor',
795
+ new BVector3(erosion.edgeColor[0], erosion.edgeColor[1], erosion.edgeColor[2])
796
+ );
797
+ }
798
+ }
799
+
800
+ /**
801
+ * Takes new live parameters (see {@link BatchedRenderer.updateSystem}): settings with the same
802
+ * shader key as the batch's, whose uniform values differ. They are copied into the batch's
803
+ * settings and written to its material, which is kept.
804
+ * @internal
805
+ */
806
+ applyLiveSettings(settings: StoredBatchSettings): void {
807
+ Object.assign(this.settings, settings);
808
+ const material = this.mesh.material;
809
+ if (material instanceof ShaderMaterial) {
810
+ this.writeLiveUniforms(material);
811
+ }
812
+ }
813
+
814
+ /**
815
+ * Writes every uniform a live parameter sets, from the batch's settings. The base writes the
816
+ * ones every render mode has; a mode adds its own.
817
+ * @internal
818
+ */
819
+ protected writeLiveUniforms(material: ShaderMaterial): void {
820
+ this.writeSoftParams(material);
821
+ this.writeSurfaceParams(material);
822
+ }
823
+
824
+ /** @internal */
825
+ addSystem(system: IParticleSystem): void {
532
826
  this.systems.add(system);
533
827
  }
534
828
 
535
- removeSystem(system: IParticleSystem) {
829
+ /** @internal */
830
+ removeSystem(system: IParticleSystem): void {
536
831
  this.systems.delete(system);
537
832
  }
538
833
 
@@ -555,21 +850,90 @@ export abstract class VFXBatch {
555
850
  return visibleSystems;
556
851
  }
557
852
 
558
- applyDepthTexture(depthTexture: BaseTexture | null): void {
853
+ /**
854
+ * Sets the depth texture soft particles read. Soft particles are compiled in only while there
855
+ * is one, as Unity turns them off for a camera without a depth texture, so attaching or
856
+ * removing it, or changing how it is packed, rebuilds a soft batch's material.
857
+ *
858
+ * @param depth - The binding, or a bare texture, read as `'linear'` and unpacked.
859
+ * @internal Use {@link BatchedRenderer.setDepthTexture}.
860
+ */
861
+ applyDepthTexture(depth: DepthBinding | BaseTexture | null): void {
862
+ if (depth instanceof BaseTexture) {
863
+ depth = {texture: depth, encoding: 'linear', packed: false};
864
+ }
865
+ const previous = this.depth;
866
+ this.depth = depth;
867
+ if (!this.settings.softParticles) {
868
+ return;
869
+ }
870
+ if (!previous !== !depth || previous?.packed !== depth?.packed) {
871
+ this.rebuildMaterial();
872
+ return;
873
+ }
559
874
  const material = this.mesh.material;
560
- if (material && material instanceof ShaderMaterial) {
561
- material.setTexture('depthTexture', depthTexture);
875
+ if (depth && material instanceof ShaderMaterial) {
876
+ material.setTexture('depthTexture', depth.texture);
877
+ }
878
+ }
879
+
880
+ /**
881
+ * Sets the soft-particle and alpha-test values on a material, and decodes the depth for the
882
+ * camera of each draw.
883
+ */
884
+ protected bindSoft(material: ShaderMaterial): void {
885
+ this.writeSoftParams(material);
886
+ if (this.drawsSoft()) {
887
+ material.setTexture('depthTexture', this.depth!.texture);
888
+ // Reused across binds: this runs on every draw call.
889
+ const projParams = new BVector4(1, 0, 0, 1);
890
+ material.onBindObservable.add(() => {
891
+ const camera = this.scene.activeCamera;
892
+ if (camera && this.depth) {
893
+ softDepthParams(camera, this.depth.encoding, projParams);
894
+ VFXBatch.writeBound(material, (target) => target.setVector4('projParams', projParams));
895
+ }
896
+ });
562
897
  }
563
898
  }
564
899
 
565
- /** Sets the light `'fixed'` mesh particles are lit by. Batches that draw no meshes ignore it. */
900
+ /** Writes the soft-particle fade and the alpha cutoff, the live parameters every mode shares. */
901
+ private writeSoftParams(material: ShaderMaterial): void {
902
+ if (this.drawsSoft()) {
903
+ material.setVector2(
904
+ 'softParams',
905
+ new BVector2(
906
+ this.settings.softNearFade,
907
+ 1.0 / Math.max(this.settings.softFarFade - this.settings.softNearFade, 0.0001)
908
+ )
909
+ );
910
+ }
911
+ if (this.settings.materialAlphaTest > 0) {
912
+ material.setFloat('alphaTest', this.settings.materialAlphaTest);
913
+ }
914
+ }
915
+
916
+ /** True when this batch draws soft particles: they are on and a depth texture is attached. */
917
+ protected drawsSoft(): boolean {
918
+ return this.settings.softParticles && this.depth !== null;
919
+ }
920
+
921
+ /**
922
+ * Sets the light `'fixed'` mesh particles are lit by. Batches that draw no meshes ignore it.
923
+ * @internal Use {@link BatchedRenderer.setMeshLighting}.
924
+ */
566
925
  applyMeshLighting(_lighting: MeshLightingSettings): void {}
567
926
 
927
+ /** @internal */
568
928
  abstract setupBuffers(): void;
929
+ /** @internal */
569
930
  abstract expandBuffers(target: number): void;
931
+ /** @internal */
570
932
  abstract rebuildMaterial(): void;
933
+ /** @internal */
571
934
  abstract update(): void;
572
935
 
936
+ /** @internal */
573
937
  dispose(): void {
574
938
  this.mesh.dispose();
575
939
  }