babylon.quarks 0.20.0 → 0.23.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.
@@ -2,6 +2,8 @@ import { BaseTexture } from '@babylonjs/core/Materials/Textures/baseTexture';
2
2
  import { Texture } from '@babylonjs/core/Materials/Textures/texture';
3
3
  import { TransformNode } from '@babylonjs/core/Meshes/transformNode';
4
4
  import { Scene } from '@babylonjs/core/scene';
5
+ import { Color3 } from '@babylonjs/core/Maths/math.color';
6
+ import { Vector3 as Vector3$1 } from '@babylonjs/core/Maths/math.vector';
5
7
  import { Mesh } from '@babylonjs/core/Meshes/mesh';
6
8
  import { PBRMaterial } from '@babylonjs/core/Materials/PBR/pbrMaterial';
7
9
  import { StandardMaterial } from '@babylonjs/core/Materials/standardMaterial';
@@ -366,6 +368,25 @@ declare const MathUtils: {
366
368
  denormalize: typeof denormalize;
367
369
  };
368
370
 
371
+ /**
372
+ * A `Quaternion` whose components live in a shared `Float32Array` at a fixed
373
+ * offset. Every `Quaternion` method reads and writes `_x` .. `_w`, so replacing
374
+ * those four with accessors inherits the whole API; they are installed on the
375
+ * prototype because TypeScript forbids overriding an inherited property with
376
+ * an accessor in the class body (TS2611).
377
+ */
378
+ declare class QuaternionView extends Quaternion {
379
+ data: Float32Array;
380
+ offset: number;
381
+ _x: number;
382
+ _y: number;
383
+ _z: number;
384
+ _w: number;
385
+ constructor(data: Float32Array, offset: number);
386
+ /** Points the view at a new backing array and row offset. */
387
+ bind(data: Float32Array, offset: number): void;
388
+ }
389
+
369
390
  /**
370
391
  * A `Vector3` whose components live in a shared `Float32Array` at a fixed
371
392
  * offset rather than in three own properties.
@@ -648,6 +669,12 @@ declare class Gradient implements FunctionColorGenerator {
648
669
  * signature check so a gradient edited in an editor is picked up.
649
670
  */
650
671
  refreshTable(): void;
672
+ /**
673
+ * The sampled table {@link genColor} currently reads from, or null while
674
+ * none has been built. Lets a compiled behavior pass interpolate the table
675
+ * inline instead of calling in here once per particle.
676
+ */
677
+ sampledTable(): Float32Array | null;
651
678
  /** Drops the sampled table, so later reads go back to evaluating the keys. */
652
679
  clearTable(): void;
653
680
  genColor(memory: GeneratorMemory, color: Vector4, t: number): Vector4;
@@ -808,6 +835,12 @@ declare class PiecewiseBezier extends PiecewiseFunction<Bezier> implements Funct
808
835
  * change. That is a handful of float compares per frame.
809
836
  */
810
837
  refreshTable(): void;
838
+ /**
839
+ * The sampled table {@link genValue} currently reads from, or null while
840
+ * none has been built. Lets a compiled behavior pass interpolate the table
841
+ * inline instead of calling in here once per particle.
842
+ */
843
+ sampledTable(): Float32Array | null;
811
844
  /** Drops the sampled table, so later reads go back to evaluating the curve. */
812
845
  clearTable(): void;
813
846
  genValue(memory: GeneratorMemory, t?: number): number;
@@ -908,6 +941,38 @@ declare class ParticleStore {
908
941
  * tightly balanced emitters flicker.
909
942
  */
910
943
  scalars: Float64Array;
944
+ /**
945
+ * Scalars the renderer uploads and the behaviors write, one per row, so a
946
+ * batch copies them as ranges instead of visiting every particle object:
947
+ * sprite rotation angle and its rate, texture tile, mirroring bits and the
948
+ * speed the particle was born with. A mesh particle keeps its quaternion
949
+ * rotation on the particle; its `rotation` row here is then unused.
950
+ */
951
+ rotation: Float32Array;
952
+ angularVelocity: Float32Array;
953
+ uvTile: Float32Array;
954
+ startSpeed: Float32Array;
955
+ /** Bit 1 mirrors along U, bit 2 along V. */
956
+ flip: Uint8Array;
957
+ /**
958
+ * Quaternion rotation and one step's quaternion turn, 4 per row, for
959
+ * particles that rotate by quaternion (mesh render mode). Empty until
960
+ * {@link ensureQuaternionColumns} is asked for them, so systems of
961
+ * billboards do not pay for columns they never read.
962
+ */
963
+ rotationQuaternion: Float32Array;
964
+ angularVelocityQuaternion: Float32Array;
965
+ /**
966
+ * Numeric copy of each particle's generator memory, {@link memoryStride}
967
+ * values per row. The spawn path calls every generator's `startGen` in a
968
+ * fixed order, so a given slot holds the same generator's sample for every
969
+ * particle, which lets a compiled behavior pass read a particle's random
970
+ * sample from a column instead of through its `memory` array. Slots that
971
+ * hold something other than a number are NaN here; readers then fall back
972
+ * to the array. Empty until {@link recordMemory} sees the first spawn.
973
+ */
974
+ memory: Float64Array;
975
+ memoryStride: number;
911
976
  /** Values per row in {@link scalars}. */
912
977
  static readonly SCALAR_STRIDE = 3;
913
978
  /** Offsets within a {@link scalars} row. */
@@ -936,7 +1001,29 @@ declare class ParticleStore {
936
1001
  * leaves every particle's view of it pointing at the old one.
937
1002
  */
938
1003
  keepPreviousVelocity(): boolean;
1004
+ /**
1005
+ * Mirrors a freshly spawned particle's generator memory into the
1006
+ * {@link memory} column, widening the column when this spawn used more
1007
+ * slots than any before it.
1008
+ */
1009
+ recordMemory(row: number, memory: ReadonlyArray<unknown>): void;
1010
+ /**
1011
+ * Whether {@link previousSize} is a snapshot of its own or an alias of
1012
+ * {@link size}. A system whose behaviors never change a particle's size
1013
+ * after birth has nothing to carry between steps for it, and aliasing the
1014
+ * two saves the copy a step would otherwise make; readers that interpolate
1015
+ * from previous to current then get the current value unchanged.
1016
+ */
1017
+ useSizeSnapshot(enabled: boolean): void;
1018
+ /** The same for {@link previousColor}, see {@link useSizeSnapshot}. */
1019
+ useColorSnapshot(enabled: boolean): void;
1020
+ /**
1021
+ * Allocates the quaternion columns. Returns true on the call that did,
1022
+ * which means views already bound to the store need re-binding.
1023
+ */
1024
+ ensureQuaternionColumns(): boolean;
939
1025
  swapRows(a: number, b: number): void;
1026
+ private static swap1;
940
1027
  private static growDouble;
941
1028
  private static swap3;
942
1029
  private static swap4;
@@ -944,8 +1031,15 @@ declare class ParticleStore {
944
1031
  }
945
1032
 
946
1033
  interface EmissionState {
1034
+ /** Bursts whose first wave has fired this loop, in time order. */
947
1035
  burstIndex: number;
1036
+ /** Wave of the burst being spawned right now (0 for its first wave). */
948
1037
  burstWaveIndex: number;
1038
+ /**
1039
+ * Waves fired so far this loop, per burst. Created by the system on first
1040
+ * use, so states made elsewhere (sub emitters) can leave it out.
1041
+ */
1042
+ burstWaves?: number[];
949
1043
  burstParticleIndex: number;
950
1044
  burstParticleCount: number;
951
1045
  isBursting: boolean;
@@ -1384,10 +1478,11 @@ declare class SpriteParticle implements Particle {
1384
1478
  */
1385
1479
  parentMatrix?: Matrix4;
1386
1480
  /**
1387
- * Initial speed of the particle.
1481
+ * Initial speed of the particle, kept in the store's `startSpeed` column.
1388
1482
  * @type {number}
1389
1483
  */
1390
- startSpeed: number;
1484
+ get startSpeed(): number;
1485
+ set startSpeed(value: number);
1391
1486
  /**
1392
1487
  * Initial color of the particle.
1393
1488
  * @type {Vector4}
@@ -1447,31 +1542,51 @@ declare class SpriteParticle implements Particle {
1447
1542
  get speedModifier(): number;
1448
1543
  set speedModifier(value: number);
1449
1544
  /**
1450
- * Angular velocity of the particle.
1545
+ * View of this particle's row in the store's quaternion rotation column,
1546
+ * when it rotates by quaternion (mesh render mode); undefined while it
1547
+ * rotates by an angle, which then lives in the store's `rotation` column.
1548
+ * Always present so every sprite particle keeps one hidden class.
1549
+ */
1550
+ private rotationQuaternion;
1551
+ /** Same split for the turn rate: a quaternion step here, a scalar rate in the store. */
1552
+ private angularVelocityQuaternion;
1553
+ /**
1554
+ * Angular velocity of the particle: a rate in radians per second for an
1555
+ * angle rotation, one step's turn for a quaternion rotation. Both live in
1556
+ * the store so the renderer and compiled behaviors read them as ranges;
1557
+ * assigning a quaternion copies it into the particle's row and returns a
1558
+ * view of that row from then on.
1451
1559
  * @type {number | Quaternion}
1452
1560
  */
1453
- angularVelocity?: number | Quaternion;
1561
+ get angularVelocity(): number | Quaternion;
1562
+ set angularVelocity(value: number | Quaternion | undefined);
1454
1563
  /**
1455
- * Rotation of the particle.
1564
+ * Rotation of the particle: an angle, or a quaternion for mesh particles.
1565
+ * Both live in the store; see {@link angularVelocity} for how assigning a
1566
+ * quaternion behaves.
1456
1567
  * @type {number | Quaternion}
1457
1568
  */
1458
- rotation: number | Quaternion;
1569
+ get rotation(): number | Quaternion;
1570
+ set rotation(value: number | Quaternion);
1459
1571
  /**
1460
1572
  * Color of the particle.
1461
1573
  * @type {Vector4}
1462
1574
  */
1463
1575
  color: Vector4;
1464
1576
  /**
1465
- * UV tile index.
1577
+ * UV tile index, kept in the store's `uvTile` column.
1466
1578
  * @type {number}
1467
1579
  */
1468
- uvTile: number;
1580
+ get uvTile(): number;
1581
+ set uvTile(value: number);
1469
1582
  /**
1470
1583
  * Mirroring picked at birth from the system's `flip` fractions: bit 1
1471
- * mirrors the sprite along U, bit 2 along V. 0 draws it as is.
1584
+ * mirrors the sprite along U, bit 2 along V. 0 draws it as is. Kept in
1585
+ * the store's `flip` column.
1472
1586
  * @type {number}
1473
1587
  */
1474
- flip: number;
1588
+ get flip(): number;
1589
+ set flip(value: number);
1475
1590
  /**
1476
1591
  * @param store - Column storage to bind to. A private single-row store is
1477
1592
  * created when omitted, so a standalone `new SpriteParticle()` still works.
@@ -1519,10 +1634,11 @@ declare class TrailParticle implements Particle {
1519
1634
  */
1520
1635
  parentMatrix?: Matrix4;
1521
1636
  /**
1522
- * Initial speed of the particle.
1637
+ * Initial speed of the particle, kept in the store's `startSpeed` column.
1523
1638
  * @type {number}
1524
1639
  */
1525
- startSpeed: number;
1640
+ get startSpeed(): number;
1641
+ set startSpeed(value: number);
1526
1642
  /**
1527
1643
  * Initial color of the particle.
1528
1644
  * @type {Vector4}
@@ -1604,16 +1720,31 @@ declare class TrailParticle implements Particle {
1604
1720
  * sample without allocating. This list is no longer populated or rendered.
1605
1721
  * @type {LinkedList<RecordState>}
1606
1722
  */
1607
- previous: LinkedList<RecordState>;
1723
+ get previous(): LinkedList<RecordState>;
1724
+ set previous(value: LinkedList<RecordState>);
1725
+ private _previous;
1608
1726
  /**
1609
- * UV tile index.
1727
+ * UV tile index, kept in the store's `uvTile` column.
1610
1728
  * @type {number}
1611
1729
  */
1612
- uvTile: number;
1730
+ get uvTile(): number;
1731
+ set uvTile(value: number);
1613
1732
  /** Ring buffer of recorded positions, 3 floats per sample. */
1614
1733
  historyPositions: Float32Array;
1615
- /** Ring buffer of recorded widths, 1 float per sample. */
1734
+ /** Ring buffer of the widths the renderer draws, 1 float per sample. */
1616
1735
  historySizes: Float32Array;
1736
+ /**
1737
+ * Ring buffer of the particle's size (`size.x`) at each sample, before any
1738
+ * behavior rescaled it. Width behaviors scale this rather than
1739
+ * `historySizes`, so their factor is never compounded step after step.
1740
+ */
1741
+ historyBaseSizes: Float32Array;
1742
+ /**
1743
+ * Factor the next recorded sample's width is drawn with — what a width
1744
+ * behavior gives the head of the trail, which is recorded after the
1745
+ * behaviors have run. 1 unless such a behavior sets it.
1746
+ */
1747
+ headWidthScale: number;
1617
1748
  /** Ring buffer of recorded colors, 4 floats per sample. */
1618
1749
  historyColors: Float32Array;
1619
1750
  /** Number of samples the ring buffers can hold. */
@@ -1790,15 +1921,30 @@ declare class ApplySequences implements Behavior {
1790
1921
  *
1791
1922
  * Each behavior walking the particle array on its own means the array, and
1792
1923
  * every particle in it, is re-read once per behavior. Merging the ones this
1793
- * module recognises into one pass reads each particle once — worth roughly
1794
- * 1.2x at a hundred thousand particles and up to 1.6x at twenty thousand,
1795
- * where the per-pass overhead weighs most.
1924
+ * module recognises into one pass reads each particle once.
1925
+ *
1926
+ * The compiled pass works on the {@link ParticleStore} columns directly. Going
1927
+ * through the particle objects instead — `particles[i].color.x` — means three
1928
+ * dependent loads through objects scattered over the heap for every number
1929
+ * touched (the particle, its `Vector4View`, the view's array), and at tens of
1930
+ * thousands of particles that pointer chasing, not the arithmetic, is what the
1931
+ * step spends its time on: measured at roughly ten times the cost of reading
1932
+ * the same numbers out of the columns. The particle object is only reached for
1933
+ * when a generator needs the particle's own memory (a per-particle random
1934
+ * sample) or holds an object-valued attribute (a quaternion rotation).
1935
+ *
1936
+ * The pass relies on the system's invariant that `particles[i]` owns row `i`
1937
+ * of the store, which the system keeps when it compacts the dead.
1796
1938
  *
1797
1939
  * A behavior that is not recognised keeps its own pass, in its original place
1798
1940
  * in the order, so nothing here depends on the built-in set being complete.
1799
1941
  */
1800
- /** Runs the compiled work for `particles[0..count)`. */
1801
- type FusedPass = (behaviors: Array<Behavior>, particles: Array<Particle>, count: number, delta: number) => void;
1942
+ /**
1943
+ * Runs the compiled work for `particles[0..count)`. `store` is the column
1944
+ * storage behind the particles; when omitted it is taken from the first
1945
+ * particle.
1946
+ */
1947
+ type FusedPass = (behaviors: Array<Behavior>, particles: Array<Particle>, count: number, delta: number, store?: ParticleStore) => void;
1802
1948
  /** One step of a plan: either a compiled run of behaviors, or a single behavior. */
1803
1949
  interface FusionStep {
1804
1950
  /** Behaviors this step covers, in order. */
@@ -1810,7 +1956,9 @@ interface FusionStep {
1810
1956
  * Splits `behaviors` into steps, merging consecutive recognised ones.
1811
1957
  *
1812
1958
  * Only consecutive behaviors are merged: a behavior that stays on its own must
1813
- * still see the particles exactly where the original order left them.
1959
+ * still see the particles exactly where the original order left them. A lone
1960
+ * recognised behavior is compiled too: the gain is not only the merged loop
1961
+ * but the column access, which its own `updateAll` does not have.
1814
1962
  */
1815
1963
  declare function planBehaviorFusion(behaviors: Array<Behavior>): Array<FusionStep>;
1816
1964
 
@@ -1889,9 +2037,18 @@ declare class EmitSubParticleSystem implements Behavior {
1889
2037
  private v_;
1890
2038
  private v2_;
1891
2039
  private subEmissions;
2040
+ /** Finished emission states, kept for reuse: a burst of deaths would otherwise allocate one plus a matrix each. */
2041
+ private spareEmissions;
1892
2042
  constructor(particleSystem: IParticleSystem, useVelocityAsBasis: boolean, subParticleSystem: IEmitter | undefined, mode?: SubParticleEmitMode, emitProbability?: number);
1893
2043
  initialize(particle: Particle): void;
1894
2044
  update(particle: Particle, delta: number): void;
2045
+ /**
2046
+ * The system to emit into, or undefined when `subParticleSystem` does not
2047
+ * hold a resolved emitter — unset, or a uuid string a loader could not
2048
+ * link. Every use goes through here, so a bad value is skipped rather
2049
+ * than thrown on.
2050
+ */
2051
+ private get target();
1895
2052
  private emit;
1896
2053
  frameUpdate(delta: number): void;
1897
2054
  toJSON(): any;
@@ -2023,6 +2180,15 @@ declare class Noise implements Behavior {
2023
2180
  duration: number;
2024
2181
  constructor(frequency: FunctionValueGenerator | ValueGenerator, power: FunctionValueGenerator | ValueGenerator, positionAmount?: FunctionValueGenerator | ValueGenerator, rotationAmount?: FunctionValueGenerator | ValueGenerator);
2025
2182
  initialize(particle: Particle): void;
2183
+ /**
2184
+ * Values of the generators that are constants, cached by {@link frameUpdate};
2185
+ * NaN where the generator has to be evaluated per particle.
2186
+ */
2187
+ private _frequency;
2188
+ private _power;
2189
+ private _positionAmount;
2190
+ private _rotationAmount;
2191
+ private static constantOf;
2026
2192
  update(particle: Particle, _: number): void;
2027
2193
  updateAll(particles: Array<Particle>, count: number, delta: number): void;
2028
2194
  toJSON(): any;
@@ -2090,14 +2256,27 @@ declare class RotationBySpeed implements Behavior {
2090
2256
 
2091
2257
  /**
2092
2258
  * Apply rotation to particles over their life.
2259
+ *
2260
+ * A billboard (rotation is an angle) turns in its own plane. A mesh particle
2261
+ * (rotation is a quaternion) turns about its local Z axis — the axis a
2262
+ * number `startRotation` turns a mesh about, and what Unity's Rotation over
2263
+ * Lifetime without separate axes does to mesh particles. For any other axis,
2264
+ * or a turn about all three, use {@link Rotation3DOverLife}.
2093
2265
  */
2094
2266
  declare class RotationOverLife implements Behavior {
2095
2267
  angularVelocity: ValueGenerator | FunctionValueGenerator;
2096
2268
  type: string;
2269
+ /** Scratch for a mesh particle's rotation step; also read by the fused pass. */
2270
+ tempQuat: Quaternion;
2097
2271
  constructor(angularVelocity: ValueGenerator | FunctionValueGenerator);
2098
2272
  initialize(particle: Particle): void;
2099
2273
  update(particle: Particle, delta: number): void;
2100
2274
  updateAll(particles: Array<Particle>, count: number, delta: number): void;
2275
+ /**
2276
+ * Turns a mesh particle by `angle` about its own Z axis, keeping the step
2277
+ * for the renderer to draw a fraction of between simulation steps.
2278
+ */
2279
+ turnAboutZ(particle: Particle, rotation: Quaternion, angle: number): void;
2101
2280
  toJSON(): any;
2102
2281
  static fromJSON(json: any): Behavior;
2103
2282
  frameUpdate(delta: number): void;
@@ -2216,6 +2395,7 @@ declare class TurbulenceField implements Behavior {
2216
2395
  constructor(scale: Vector3, octaves: number, velocityMultiplier: Vector3, timeScale: Vector3);
2217
2396
  initialize(particle: Particle): void;
2218
2397
  update(particle: Particle, delta: number): void;
2398
+ updateAll(particles: Array<Particle>, count: number, delta: number): void;
2219
2399
  toJSON(): any;
2220
2400
  frameUpdate(delta: number): void;
2221
2401
  static fromJSON(json: any): Behavior;
@@ -2268,13 +2448,29 @@ declare class VelocityOverLife implements Behavior {
2268
2448
  }
2269
2449
 
2270
2450
  /**
2271
- * Apply width to particles based on their length.
2451
+ * Shapes a trail's width along its length — Unity's Width over Trail.
2452
+ *
2453
+ * The curve runs from the head (0) to the tail (1) and, by default, is a
2454
+ * multiplier on the width each sample was recorded with (the particle's
2455
+ * `size.x` at that moment): a curve from 1 to 0 tapers the trail from the
2456
+ * particle's size at the head down to nothing at the tail, whatever that size
2457
+ * is. The head itself gets the curve's value at 0.
2458
+ *
2459
+ * With `absolute` set the curve is the width itself, in world units, ignoring
2460
+ * the particle's size; the head keeps the particle's size. That is how this
2461
+ * behavior read every curve up to 0.21, and an effect JSON written by those
2462
+ * versions (it has no `absolute` field) still loads that way.
2272
2463
  */
2273
2464
  declare class WidthOverLength implements Behavior {
2274
2465
  width: FunctionValueGenerator;
2466
+ absolute: boolean;
2275
2467
  type: string;
2276
2468
  initialize(particle: Particle): void;
2277
- constructor(width: FunctionValueGenerator);
2469
+ /**
2470
+ * @param width - Width along the trail, head (0) to tail (1).
2471
+ * @param absolute - Read `width` as world units instead of a multiplier on the particle's size.
2472
+ */
2473
+ constructor(width: FunctionValueGenerator, absolute?: boolean);
2278
2474
  update(particle: Particle): void;
2279
2475
  frameUpdate(delta: number): void;
2280
2476
  toJSON(): any;
@@ -2954,6 +3150,56 @@ declare enum RenderMode {
2954
3150
  HorizontalBillBoard = 4,
2955
3151
  VerticalBillBoard = 5
2956
3152
  }
3153
+ /**
3154
+ * How mesh particles are lit.
3155
+ *
3156
+ * - `'fixed'` (default): one directional light plus ambient, the same for every
3157
+ * mesh batch of a renderer, set with {@link BatchedRenderer.setMeshLighting}.
3158
+ * - `'unlit'`: no lighting term — the texture times the particle colour, for
3159
+ * emissive meshes (fire, energy, glowing blades).
3160
+ * - `'scene'`: follows the scene's first enabled `DirectionalLight` or
3161
+ * `HemisphericLight` every frame, falling back to the fixed light when there
3162
+ * is none.
3163
+ *
3164
+ * Only mesh particles are lit; other render modes ignore it.
3165
+ */
3166
+ type MeshLighting = 'unlit' | 'fixed' | 'scene';
3167
+ /** The light {@link MeshLighting} `'fixed'` mesh particles are lit by. */
3168
+ interface MeshLightingSettings {
3169
+ /** Direction the light travels in, world space. Default `(0.4, -1, 0.6)`. */
3170
+ direction: Vector3$1;
3171
+ /** Light colour, intensity folded in. Default white. */
3172
+ color: Color3;
3173
+ /** Light every face gets regardless of its direction. Default `(0.35, 0.35, 0.35)`. */
3174
+ ambient: Color3;
3175
+ }
3176
+ /** The light mesh particles have always been lit by. */
3177
+ declare function defaultMeshLighting(): MeshLightingSettings;
3178
+ /**
3179
+ * A rim (Fresnel) term on mesh particles: a colour that grows where the surface
3180
+ * turns away from the camera, `pow(1 - |N·V|, power) * strength`. Unset: no rim.
3181
+ * Only mesh particles draw it.
3182
+ */
3183
+ interface MeshRim {
3184
+ /** Edge colour, linear RGB. Default `[1, 1, 1]`. */
3185
+ color: [number, number, number];
3186
+ /** Falloff exponent; higher keeps the rim to a thinner edge. Default 2. */
3187
+ power: number;
3188
+ /** How much of the rim is applied, usually 0…1. Default 1. */
3189
+ strength: number;
3190
+ /**
3191
+ * `'add'` (default) adds the colour, for a glowing edge; `'mix'` blends the
3192
+ * surface toward it, for an outline (a dark ink line with a dark colour).
3193
+ */
3194
+ mode: 'add' | 'mix';
3195
+ }
3196
+ /**
3197
+ * A complete rim from a partial one, every field defaulted and kept in range,
3198
+ * or undefined when `rim` is unset.
3199
+ */
3200
+ declare function normalizeMeshRim(rim: Partial<MeshRim> | null | undefined): MeshRim | undefined;
3201
+ /** True when both describe the same rim (or both none). */
3202
+ declare function meshRimEqual(a: MeshRim | undefined, b: MeshRim | undefined): boolean;
2957
3203
  interface StoredBatchSettings {
2958
3204
  instancingGeometry: Float32Array;
2959
3205
  instancingIndices: Uint32Array | Uint16Array;
@@ -2980,6 +3226,10 @@ interface StoredBatchSettings {
2980
3226
  layerMask: number;
2981
3227
  /** Billboard pivot in particle-size units, see {@link VFXBatchSettings.pivot}. */
2982
3228
  pivot?: Vector3;
3229
+ /** Mesh particle lighting, see {@link VFXBatchSettings.lighting}. */
3230
+ lighting?: MeshLighting;
3231
+ /** Mesh particle rim, see {@link VFXBatchSettings.rim}. */
3232
+ rim?: MeshRim;
2983
3233
  }
2984
3234
  declare abstract class VFXBatch {
2985
3235
  mesh: Mesh;
@@ -2998,6 +3248,8 @@ declare abstract class VFXBatch {
2998
3248
  */
2999
3249
  getVisibleSystems(): IParticleSystem[];
3000
3250
  applyDepthTexture(depthTexture: BaseTexture | null): void;
3251
+ /** Sets the light `'fixed'` mesh particles are lit by. Batches that draw no meshes ignore it. */
3252
+ applyMeshLighting(_lighting: MeshLightingSettings): void;
3001
3253
  abstract setupBuffers(): void;
3002
3254
  abstract expandBuffers(target: number): void;
3003
3255
  abstract rebuildMaterial(): void;
@@ -3052,6 +3304,13 @@ interface VFXBatchSettings {
3052
3304
  * y across, and z is unused. Absent or zero: no offset. Meshes ignore it.
3053
3305
  */
3054
3306
  pivot?: Vector3;
3307
+ /**
3308
+ * How mesh particles are lit (`'unlit'`, `'fixed'` or `'scene'`); unset is
3309
+ * `'fixed'`. See {@link MeshLighting}. Other render modes ignore it.
3310
+ */
3311
+ lighting?: MeshLighting;
3312
+ /** Rim (Fresnel) term on mesh particles; unset draws none. See {@link MeshRim}. */
3313
+ rim?: MeshRim;
3055
3314
  }
3056
3315
  interface AdaptivePerformanceOptions {
3057
3316
  targetFrameMs: number;
@@ -3094,6 +3353,7 @@ declare class BatchedRenderer extends TransformNode {
3094
3353
  private static readonly EMPTY_BATCH_GRACE_FRAMES;
3095
3354
  private adaptivePerformanceState;
3096
3355
  private lastAppliedQuality;
3356
+ private meshLighting;
3097
3357
  constructor(name: string, scene: Scene);
3098
3358
  private static clamp;
3099
3359
  private static equals;
@@ -3109,6 +3369,20 @@ declare class BatchedRenderer extends TransformNode {
3109
3369
  */
3110
3370
  updateSystem(system: IParticleSystem): void;
3111
3371
  setDepthTexture(depthTexture: BaseTexture | null): void;
3372
+ /**
3373
+ * Sets the light mesh particles with `lighting: 'fixed'` (the default) are
3374
+ * lit by, for every mesh batch of this renderer, including ones created
3375
+ * later. Fields left out keep their current value. Also the fallback for
3376
+ * `'scene'` lighting when the scene has no directional or hemispheric light.
3377
+ *
3378
+ * @example
3379
+ * ```ts
3380
+ * renderer.setMeshLighting({direction: new Vector3(-0.3, -1, 0.2), color: new Color3(1, 0.9, 0.75)});
3381
+ * ```
3382
+ */
3383
+ setMeshLighting(lighting: Partial<MeshLightingSettings>): void;
3384
+ /** A copy of the light set with {@link setMeshLighting}. */
3385
+ getMeshLighting(): MeshLightingSettings;
3112
3386
  configureAdaptivePerformance(options?: Partial<AdaptivePerformanceOptions>): void;
3113
3387
  disableAdaptivePerformance(resetQuality?: boolean): void;
3114
3388
  getAdaptivePerformanceState(): AdaptivePerformanceState;
@@ -3265,6 +3539,10 @@ interface ParticleSystemParameters {
3265
3539
  pivot?: Vector3;
3266
3540
  /** Fraction of billboard particles mirrored along U (x) and V (y), see {@link ParticleSystem.flip}. */
3267
3541
  flip?: Vector2;
3542
+ /** How mesh particles are lit, see {@link ParticleSystem.lighting}. Default `'fixed'`. */
3543
+ lighting?: MeshLighting;
3544
+ /** Rim (Fresnel) term on mesh particles, see {@link ParticleSystem.rim}. Default none. */
3545
+ rim?: Partial<MeshRim>;
3268
3546
  scene?: Scene;
3269
3547
  }
3270
3548
  interface BurstParametersJSON {
@@ -3392,6 +3670,8 @@ declare class ParticleSystem implements IParticleSystem {
3392
3670
  private previousEmitterPos?;
3393
3671
  private tempEmitterPos;
3394
3672
  private temp;
3673
+ /** Emitter matrix moved along the step's path, see spawnAlongPath. */
3674
+ private pathMatrix;
3395
3675
  private normalMatrix;
3396
3676
  private memory;
3397
3677
  private listeners;
@@ -3404,6 +3684,12 @@ declare class ParticleSystem implements IParticleSystem {
3404
3684
  /** Behaviors the cached plan was built from, compared by identity. */
3405
3685
  private fusionPlan?;
3406
3686
  private fusionSteps;
3687
+ /**
3688
+ * Whether any behavior in the plan can change a particle's size or colour
3689
+ * after birth. Only then does a step have to snapshot them for the renderer.
3690
+ */
3691
+ private planChangesSize;
3692
+ private planChangesColor;
3407
3693
  /** @internal **/
3408
3694
  _renderer?: BatchedRenderer;
3409
3695
  set time(time: number);
@@ -3435,6 +3721,27 @@ declare class ParticleSystem implements IParticleSystem {
3435
3721
  */
3436
3722
  get pivot(): Vector3 | undefined;
3437
3723
  set pivot(v: Vector3 | undefined);
3724
+ /**
3725
+ * How mesh particles are lit: `'fixed'` (the default — the renderer's
3726
+ * light, see {@link BatchedRenderer.setMeshLighting}), `'unlit'` (texture
3727
+ * times colour, for emissive meshes) or `'scene'` (the scene's first
3728
+ * directional or hemispheric light). Saved with the material in the effect
3729
+ * JSON. Other render modes ignore it.
3730
+ */
3731
+ get lighting(): MeshLighting;
3732
+ set lighting(lighting: MeshLighting);
3733
+ /**
3734
+ * Rim (Fresnel) term on mesh particles — a glowing edge (`mode: 'add'`) or
3735
+ * an outline (`'mix'`), see {@link MeshRim}. Undefined (the default) draws
3736
+ * none. Fields left out of an assigned rim take their defaults; assign a
3737
+ * new object to change it, editing the returned one does not reach the
3738
+ * renderer. Saved with the material in the effect JSON. Other render modes
3739
+ * ignore it.
3740
+ */
3741
+ get rim(): MeshRim | undefined;
3742
+ set rim(rim: Partial<MeshRim> | undefined);
3743
+ /** `lighting` if it is one of the modes, else the default. */
3744
+ private static validLighting;
3438
3745
  private static nonZeroPivot;
3439
3746
  private static nonZeroFlip;
3440
3747
  get softNearFade(): number;
@@ -3513,6 +3820,40 @@ declare class ParticleSystem implements IParticleSystem {
3513
3820
  /** Advances emission, behaviors, motion and culling by one fixed timestep. */
3514
3821
  private simulateStep;
3515
3822
  private notifyFinished;
3823
+ /** Waves a burst fires per loop: its `cycle`, and never fewer than one. */
3824
+ private static burstWaveCount;
3825
+ /** Seconds between a burst's waves; a missing or negative interval fires them all at once. */
3826
+ private static burstInterval;
3827
+ /**
3828
+ * Fires the burst waves that are due by `emissionState.time`.
3829
+ *
3830
+ * Wave `w` of a burst is due at `burst.time + w * burst.interval`, and the
3831
+ * burst fires `cycle` of them, each rolling `probability` on its own — the
3832
+ * way Unity repeats a burst. Bursts start in time order, as they always
3833
+ * have; their repeats are tracked per burst so a burst that is still
3834
+ * repeating never holds back a later one. Repeats falling past the
3835
+ * system's duration are dropped (a looping system starts them over on its
3836
+ * next loop), matching Unity, which never emits a burst cycle beyond the
3837
+ * end of the duration.
3838
+ */
3839
+ private emitBursts;
3840
+ /** Spawns one wave of `burst`, if its probability roll passes. */
3841
+ private fireBurstWave;
3842
+ /** True while a burst that has started still has a wave due within the duration. */
3843
+ private hasPendingBurstWaves;
3844
+ /**
3845
+ * Spawns `count` particles owed to emission over distance, each where the
3846
+ * emitter crossed its metre mark on the way from `from` to where
3847
+ * `emitterMatrix` has it now, rather than all of them at the end of the
3848
+ * path: a fast emitter then leaves a continuous trail instead of one clump
3849
+ * per step.
3850
+ *
3851
+ * `carried` is how far the emitter had already gone past its last mark
3852
+ * before this step, so the marks keep their spacing across steps. Only the
3853
+ * position is interpolated; the emitter's current rotation and scale are
3854
+ * used for every particle.
3855
+ */
3856
+ private spawnAlongPath;
3516
3857
  emit(delta: number, emissionState: EmissionState, emitterMatrix: Matrix4): void;
3517
3858
  toJSON(metaData?: BabylonMetaData | string, _options?: SerializationOptions): ParticleSystemJSONParameters;
3518
3859
  static fromJSON(json: ParticleSystemJSONParameters, meta: BabylonMetaData, dependencies?: {
@@ -3535,6 +3876,18 @@ declare class ParticleSystem implements IParticleSystem {
3535
3876
  clone(): ParticleSystem;
3536
3877
  }
3537
3878
 
3879
+ /** Kind of linked asset the loader asks a {@link QuarksResourceResolver} to handle. */
3880
+ type QuarksResourceKind = 'effect' | 'geometry' | 'image';
3881
+ /**
3882
+ * Hooks for resolving relative source links (textures, geometry files) and loading their JSON.
3883
+ * Omit either method to keep the default (`baseUrl` join / `fetch`).
3884
+ */
3885
+ interface QuarksResourceResolver {
3886
+ /** Resolve a relative url to an absolute / blob / data url. Default: {@link QuarksLoader.joinUrl}. */
3887
+ resolve?(url: string, kind: QuarksResourceKind, baseUrl: string): string | Promise<string>;
3888
+ /** Load a JSON asset (geometry files). Default: `fetch(resolvedUrl).json()`. */
3889
+ fetchJson?(resolvedUrl: string): Promise<unknown>;
3890
+ }
3538
3891
  interface QuarksLoaderOptions {
3539
3892
  baseUrl?: string;
3540
3893
  /**
@@ -3546,19 +3899,96 @@ interface QuarksLoaderOptions {
3546
3899
  * change one effect's texture settings without touching the others.
3547
3900
  */
3548
3901
  shareResources?: boolean;
3902
+ /** Custom url / fetch backend (File picker, CDN, IndexedDB). */
3903
+ resolver?: QuarksResourceResolver;
3904
+ }
3905
+ /** One in-memory effect for {@link QuarksLoader.loadFromJSONAll}. */
3906
+ interface QuarksJSONLoadEntry {
3907
+ json: unknown;
3908
+ /** Overrides loader `baseUrl` for relative texture / geometry links in this effect. */
3909
+ baseUrl?: string;
3910
+ }
3911
+ /**
3912
+ * Options for {@link QuarksLoader.loadAll} / {@link QuarksLoader.loadFromJSONAll}.
3913
+ * Batch loads share the per-scene fetch cache and `shareResources`, so N effects that point at
3914
+ * the same `assets/geometries/*.json` or texture only pay for that asset once.
3915
+ */
3916
+ interface QuarksBatchLoadOptions {
3917
+ /**
3918
+ * Max concurrent effect-document fetches (phase 1). Linked geometry/image resolve still runs
3919
+ * across the whole pack in parallel so shared urls coalesce. Default: all at once.
3920
+ */
3921
+ concurrency?: number;
3922
+ /**
3923
+ * Called once per effect when it has fully settled (parsed successfully, or failed under
3924
+ * `continueOnError`). `done` is how many of `total` have settled so far.
3925
+ */
3926
+ onProgress?: (done: number, total: number) => void;
3927
+ /**
3928
+ * When true, a failed effect becomes `null` in the result array and the rest keep loading.
3929
+ * When false (default), the first failure rejects the whole batch.
3930
+ */
3931
+ continueOnError?: boolean;
3549
3932
  }
3550
3933
  declare class QuarksLoader {
3551
3934
  private scene;
3552
3935
  private options;
3553
3936
  constructor(scene: Scene, options?: QuarksLoaderOptions);
3937
+ /** Fetches an effect JSON from `url`, resolves linked sources, and parses it. */
3554
3938
  load(url: string): Promise<TransformNode>;
3939
+ /**
3940
+ * Resolves linked geometry / image urls on `json` (via {@link resolveResources}) then parses.
3941
+ * Use when the effect JSON is already in hand (File, CDN payload, editor import).
3942
+ */
3943
+ loadFromJSON(json: any, baseUrl?: string): Promise<TransformNode>;
3944
+ /**
3945
+ * Loads many effects in parallel for battle / level preload. Phase 1 fetches every effect
3946
+ * JSON (optionally concurrency-limited); phase 2 resolves all linked geometries/images across
3947
+ * the pack together so shared asset urls hit the fetch cache once; phase 3 parses.
3948
+ *
3949
+ * @example
3950
+ * ```ts
3951
+ * const roots = await loader.loadAll([
3952
+ * 'fx/hit.json', 'fx/explosion.json', 'fx/heal.json',
3953
+ * ], { concurrency: 6, continueOnError: true });
3954
+ * ```
3955
+ */
3956
+ loadAll(urls: string[], options?: QuarksBatchLoadOptions & {
3957
+ continueOnError?: false;
3958
+ }): Promise<TransformNode[]>;
3959
+ loadAll(urls: string[], options: QuarksBatchLoadOptions & {
3960
+ continueOnError: true;
3961
+ }): Promise<(TransformNode | null)[]>;
3962
+ /**
3963
+ * Same two-phase batch as {@link loadAll}, but effect JSON is already in memory (asset pack,
3964
+ * IndexedDB, editor). Entries may be bare JSON or `{ json, baseUrl }`.
3965
+ */
3966
+ loadFromJSONAll(entries: Array<QuarksJSONLoadEntry | unknown>, options?: QuarksBatchLoadOptions & {
3967
+ continueOnError?: false;
3968
+ }): Promise<TransformNode[]>;
3969
+ loadFromJSONAll(entries: Array<QuarksJSONLoadEntry | unknown>, options: QuarksBatchLoadOptions & {
3970
+ continueOnError: true;
3971
+ }): Promise<(TransformNode | null)[]>;
3972
+ /**
3973
+ * Inlines external geometry files and rewrites relative image urls through the loader's
3974
+ * {@link QuarksResourceResolver}. Idempotent for already-inlined geometries. Failures warn
3975
+ * and leave the entry as is.
3976
+ */
3977
+ resolveResources(json: any, baseUrl?: string): Promise<void>;
3978
+ /**
3979
+ * Same as instance {@link resolveResources}, but usable without constructing a loader (e.g.
3980
+ * editor import that only prepares JSON). Pass `scene` to share the per-scene fetch cache.
3981
+ */
3982
+ static resolveLinkedResources(json: any, options?: {
3983
+ baseUrl?: string;
3984
+ resolver?: QuarksResourceResolver;
3985
+ scene?: Scene;
3986
+ }): Promise<void>;
3555
3987
  /**
3556
3988
  * Inlines geometries that an export wrote to their own file (`{uuid, type: 'QuarksGeometry',
3557
- * url}` — e.g. the Unity exporter's "geometries as files"): each `url` is passed to
3558
- * `loadGeometry`, and the `positions / indices / uvs / normals` it returns replace the url in
3559
- * place, so `parse()` (which is synchronous) sees an ordinary inline geometry. `load()` does
3560
- * this with `fetch`; call it yourself before `parse()` when the files come from elsewhere.
3561
- * A geometry that fails to load is left as is and warned about.
3989
+ * url}`). Prefer {@link load}, {@link loadFromJSON}, or {@link resolveResources} — those use
3990
+ * the loader's resolver and fetch cache. Kept for callers that already have a custom
3991
+ * `loadGeometry` and no `QuarksLoader` instance.
3562
3992
  */
3563
3993
  static resolveExternalGeometries(json: any, loadGeometry: (url: string) => Promise<any>): Promise<void>;
3564
3994
  parse(json: any, baseUrl?: string): TransformNode;
@@ -3566,15 +3996,31 @@ declare class QuarksLoader {
3566
3996
  /** The scene's copy of an identical geometry when resources are shared, else `geometry` itself. */
3567
3997
  private share;
3568
3998
  /**
3569
- * Forgets the textures and geometries shared between loads into `scene` (see
3570
- * {@link QuarksLoaderOptions.shareResources}). Effects already loaded keep theirs; the next
3571
- * load creates new ones. Textures are not disposed — they may still be in use.
3999
+ * Forgets the textures, geometries, and in-flight JSON fetches shared between loads into
4000
+ * `scene` (see {@link QuarksLoaderOptions.shareResources}). Effects already loaded keep
4001
+ * theirs; the next load creates new ones. Textures are not disposed — they may still be in use.
3572
4002
  */
3573
4003
  static clearSharedResources(scene: Scene): void;
4004
+ /** Joins `baseUrl` with a relative `url`; leaves data/blob/absolute/root-absolute urls alone. */
4005
+ static joinUrl(baseUrl: string, url: string): string;
3574
4006
  private parseSphereGeometry;
3575
4007
  private parseBufferGeometry;
3576
4008
  private extractInterleavedAttribute;
3577
4009
  private int32ArrayToFloat32;
4010
+ private resolveResourceUrl;
4011
+ /** Fetches one effect JSON; documents are not fetch-cached because resolve mutates them. */
4012
+ private fetchEffectDocument;
4013
+ /**
4014
+ * Resolve every document's linked resources in parallel (shared urls coalesce via the
4015
+ * scene fetch cache), then parse. Each index settles exactly once for `onProgress`.
4016
+ */
4017
+ private loadDocuments;
4018
+ /** Runs `fn` over `items` with at most `concurrency` in flight (or all if unset/≤0). */
4019
+ private static mapPool;
4020
+ private static resolveWith;
4021
+ /** One in-flight fetch per scene + resolved url when `scene` is set; otherwise uncached. */
4022
+ private static fetchJsonWith;
4023
+ private static inlineGeometryEntries;
3578
4024
  private parseImages;
3579
4025
  /**
3580
4026
  * Flags an image entry that is plainly not an image, which otherwise only
@@ -3586,13 +4032,6 @@ declare class QuarksLoader {
3586
4032
  * file backs.
3587
4033
  */
3588
4034
  private static warnIfNotAnImageUrl;
3589
- /**
3590
- * `img.url` is already absolute for data/blob URIs and full http(s) URLs — common for
3591
- * "portable" exports that embed textures as base64 (see ensurePortableTextureUrl in
3592
- * babylon.quarks-editor). Prepending baseUrl to those turns e.g.
3593
- * "data:image/png;base64,AAAA" into an invalid path like "/some/dir/data:image/png;base64,AAAA".
3594
- */
3595
- private static resolveUrl;
3596
4035
  private parseTextures;
3597
4036
  private mapWrapMode;
3598
4037
  private parseMaterials;
@@ -3672,7 +4111,7 @@ declare const _default$6: "\n// Per-vertex attributes\nattribute vec3 position;\
3672
4111
 
3673
4112
  declare const _default$5: "\nvarying vec2 vUV;\nvarying vec4 vColor;\n\n#ifdef TILE_BLEND\nvarying vec2 vUV2;\nvarying float vTileBlend;\n#endif\n\n#ifdef USE_MAP\nuniform sampler2D map;\n#endif\n\n#ifdef SOFT_PARTICLES\nuniform sampler2D depthTexture;\nuniform vec2 softParams;\nuniform vec4 projParams;\nvarying vec4 projPosition;\nvarying float linearDepth;\n#endif\n\n#ifdef USE_ALPHATEST\nuniform float alphaTest;\n#endif\n\nvoid main() {\n vec4 baseColor = vColor;\n\n#ifdef USE_MAP\n vec4 texColor = texture2D(map, vUV);\n #ifdef TILE_BLEND\n vec4 texColor2 = texture2D(map, vUV2);\n texColor = mix(texColor, texColor2, vTileBlend);\n #endif\n baseColor *= texColor;\n#endif\n\n#ifdef USE_COLOR_AS_ALPHA\n baseColor.a *= (baseColor.r + baseColor.g + baseColor.b) / 3.0;\n#endif\n\n#ifdef USE_ALPHATEST\n if (baseColor.a < alphaTest) discard;\n#else\n if (baseColor.a < 0.01) discard;\n#endif\n\n gl_FragColor = baseColor;\n\n#ifdef SOFT_PARTICLES\n vec2 p2 = projPosition.xy / projPosition.w;\n p2 = 0.5 * p2 + 0.5;\n float readDepth = texture2D(depthTexture, p2.xy).r;\n float zNear = projParams.x;\n float zFar = projParams.y;\n float viewDepth = (zFar * zNear) / (zFar - readDepth * (zFar - zNear));\n float fade = clamp(softParams.y * ((viewDepth - softParams.x) - linearDepth), 0.0, 1.0);\n gl_FragColor *= fade;\n#endif\n}\n";
3674
4113
 
3675
- declare const _default$4: "\nvarying vec2 vUV;\nvarying vec4 vColor;\nvarying vec3 vNormal;\nvarying vec3 vWorldPos;\n\nuniform vec3 lightDirection;\nuniform vec3 lightColor;\nuniform vec3 ambientColor;\n\n#ifdef TILE_BLEND\nvarying vec2 vUV2;\nvarying float vTileBlend;\n#endif\n\n#ifdef USE_MAP\nuniform sampler2D map;\n#endif\n\n// One 3\u00D72 atlas (px py pz / nx ny nz). Avoids samplerCube and multi-face\n// sampler bindings that raise GL_INVALID_OPERATION on iOS WebKit.\n#ifdef USE_ENVMAP_ATLAS\nuniform sampler2D envAtlas;\nuniform vec3 eyePosition;\nuniform float reflectionLevel;\n\nvec3 sampleEnvAtlas(vec3 r) {\n vec3 a = abs(r);\n vec2 localUV;\n float cellX;\n float cellY;\n // Atlas layout: +X +Y +Z on row 0, -X -Y -Z on row 1.\n if (a.x >= a.y && a.x >= a.z) {\n cellX = 0.0;\n if (r.x >= 0.0) {\n cellY = 0.0;\n localUV = vec2(-r.z, -r.y) / a.x * 0.5 + 0.5;\n } else {\n cellY = 1.0;\n localUV = vec2(r.z, -r.y) / a.x * 0.5 + 0.5;\n }\n } else if (a.y >= a.z) {\n cellX = 1.0;\n if (r.y >= 0.0) {\n cellY = 0.0;\n localUV = vec2(r.x, r.z) / a.y * 0.5 + 0.5;\n } else {\n cellY = 1.0;\n localUV = vec2(r.x, -r.z) / a.y * 0.5 + 0.5;\n }\n } else {\n cellX = 2.0;\n if (r.z >= 0.0) {\n cellY = 0.0;\n localUV = vec2(r.x, -r.y) / a.z * 0.5 + 0.5;\n } else {\n cellY = 1.0;\n localUV = vec2(-r.x, -r.y) / a.z * 0.5 + 0.5;\n }\n }\n vec2 uv = (vec2(cellX, cellY) + clamp(localUV, 0.0, 1.0)) / vec2(3.0, 2.0);\n return texture2D(envAtlas, uv).rgb;\n}\n#endif\n\n#ifdef SOFT_PARTICLES\nuniform sampler2D depthTexture;\nuniform vec2 softParams;\nuniform vec4 projParams;\nvarying vec4 projPosition;\nvarying float linearDepth;\n#endif\n\n#ifdef USE_ALPHATEST\nuniform float alphaTest;\n#endif\n\nvoid main() {\n vec4 baseColor = vColor;\n\n#ifdef USE_MAP\n vec4 texColor = texture2D(map, vUV);\n #ifdef TILE_BLEND\n vec4 texColor2 = texture2D(map, vUV2);\n texColor = mix(texColor, texColor2, vTileBlend);\n #endif\n baseColor *= texColor;\n#endif\n\n#ifdef USE_ALPHATEST\n if (baseColor.a < alphaTest) discard;\n#else\n if (baseColor.a < 0.01) discard;\n#endif\n\n vec3 N = normalize(vNormal);\n vec3 L = normalize(-lightDirection);\n float NdotL = max(dot(N, L), 0.0);\n vec3 litColor = ambientColor + lightColor * NdotL;\n\n vec3 color = baseColor.rgb * litColor;\n\n#ifdef USE_ENVMAP_ATLAS\n vec3 viewDir = normalize(vWorldPos - eyePosition);\n vec3 reflectionCoords = reflect(viewDir, N);\n color += sampleEnvAtlas(reflectionCoords) * reflectionLevel;\n#endif\n\n gl_FragColor = vec4(color, baseColor.a);\n\n#ifdef SOFT_PARTICLES\n vec2 p2 = projPosition.xy / projPosition.w;\n p2 = 0.5 * p2 + 0.5;\n float readDepth = texture2D(depthTexture, p2.xy).r;\n float zNear = projParams.x;\n float zFar = projParams.y;\n float viewDepth = (zFar * zNear) / (zFar - readDepth * (zFar - zNear));\n float fade = clamp(softParams.y * ((viewDepth - softParams.x) - linearDepth), 0.0, 1.0);\n gl_FragColor *= fade;\n#endif\n}\n";
4114
+ declare const _default$4: "\nvarying vec2 vUV;\nvarying vec4 vColor;\nvarying vec3 vNormal;\nvarying vec3 vWorldPos;\n\n#ifndef MESH_UNLIT\nuniform vec3 lightDirection;\nuniform vec3 lightColor;\nuniform vec3 ambientColor;\n#endif\n\n#ifdef TILE_BLEND\nvarying vec2 vUV2;\nvarying float vTileBlend;\n#endif\n\n#ifdef USE_MAP\nuniform sampler2D map;\n#endif\n\n#if defined(USE_ENVMAP_ATLAS) || defined(MESH_RIM)\nuniform vec3 eyePosition;\n#endif\n\n#ifdef MESH_RIM\nuniform vec3 rimColor;\nuniform vec2 rimParams;\n#endif\n\n// One 3\u00D72 atlas (px py pz / nx ny nz). Avoids samplerCube and multi-face\n// sampler bindings that raise GL_INVALID_OPERATION on iOS WebKit.\n#ifdef USE_ENVMAP_ATLAS\nuniform sampler2D envAtlas;\nuniform float reflectionLevel;\n\nvec3 sampleEnvAtlas(vec3 r) {\n vec3 a = abs(r);\n vec2 localUV;\n float cellX;\n float cellY;\n // Atlas layout: +X +Y +Z on row 0, -X -Y -Z on row 1.\n if (a.x >= a.y && a.x >= a.z) {\n cellX = 0.0;\n if (r.x >= 0.0) {\n cellY = 0.0;\n localUV = vec2(-r.z, -r.y) / a.x * 0.5 + 0.5;\n } else {\n cellY = 1.0;\n localUV = vec2(r.z, -r.y) / a.x * 0.5 + 0.5;\n }\n } else if (a.y >= a.z) {\n cellX = 1.0;\n if (r.y >= 0.0) {\n cellY = 0.0;\n localUV = vec2(r.x, r.z) / a.y * 0.5 + 0.5;\n } else {\n cellY = 1.0;\n localUV = vec2(r.x, -r.z) / a.y * 0.5 + 0.5;\n }\n } else {\n cellX = 2.0;\n if (r.z >= 0.0) {\n cellY = 0.0;\n localUV = vec2(r.x, -r.y) / a.z * 0.5 + 0.5;\n } else {\n cellY = 1.0;\n localUV = vec2(-r.x, -r.y) / a.z * 0.5 + 0.5;\n }\n }\n vec2 uv = (vec2(cellX, cellY) + clamp(localUV, 0.0, 1.0)) / vec2(3.0, 2.0);\n return texture2D(envAtlas, uv).rgb;\n}\n#endif\n\n#ifdef SOFT_PARTICLES\nuniform sampler2D depthTexture;\nuniform vec2 softParams;\nuniform vec4 projParams;\nvarying vec4 projPosition;\nvarying float linearDepth;\n#endif\n\n#ifdef USE_ALPHATEST\nuniform float alphaTest;\n#endif\n\nvoid main() {\n vec4 baseColor = vColor;\n\n#ifdef USE_MAP\n vec4 texColor = texture2D(map, vUV);\n #ifdef TILE_BLEND\n vec4 texColor2 = texture2D(map, vUV2);\n texColor = mix(texColor, texColor2, vTileBlend);\n #endif\n baseColor *= texColor;\n#endif\n\n#ifdef USE_ALPHATEST\n if (baseColor.a < alphaTest) discard;\n#else\n if (baseColor.a < 0.01) discard;\n#endif\n\n vec3 N = normalize(vNormal);\n#ifdef MESH_UNLIT\n vec3 color = baseColor.rgb;\n#else\n vec3 L = normalize(-lightDirection);\n float NdotL = max(dot(N, L), 0.0);\n vec3 litColor = ambientColor + lightColor * NdotL;\n\n vec3 color = baseColor.rgb * litColor;\n#endif\n\n#ifdef USE_ENVMAP_ATLAS\n vec3 viewDir = normalize(vWorldPos - eyePosition);\n vec3 reflectionCoords = reflect(viewDir, N);\n color += sampleEnvAtlas(reflectionCoords) * reflectionLevel;\n#endif\n\n#ifdef MESH_RIM\n // Fresnel: strongest where the surface turns away from the camera.\n vec3 V = normalize(eyePosition - vWorldPos);\n float rim = pow(1.0 - abs(dot(N, V)), rimParams.x) * rimParams.y;\n #ifdef MESH_RIM_MIX\n color = mix(color, rimColor, clamp(rim, 0.0, 1.0));\n #else\n color += rimColor * rim;\n #endif\n#endif\n\n gl_FragColor = vec4(color, baseColor.a);\n\n#ifdef SOFT_PARTICLES\n vec2 p2 = projPosition.xy / projPosition.w;\n p2 = 0.5 * p2 + 0.5;\n float readDepth = texture2D(depthTexture, p2.xy).r;\n float zNear = projParams.x;\n float zFar = projParams.y;\n float viewDepth = (zFar * zNear) / (zFar - readDepth * (zFar - zNear));\n float fade = clamp(softParams.y * ((viewDepth - softParams.x) - linearDepth), 0.0, 1.0);\n gl_FragColor *= fade;\n#endif\n}\n";
3676
4115
 
3677
4116
  declare const _default$3: "\n// Per-vertex attributes\nattribute vec3 position;\nattribute vec2 uv;\n\n// Per-instance attributes\nattribute vec3 offset;\nattribute vec4 color;\nattribute vec3 size;\nattribute float rotation;\nattribute float uvTile;\n\n// Uniforms\nuniform mat4 world;\nuniform mat4 view;\nuniform mat4 projection;\nuniform mat4 worldViewProjection;\n\n#ifdef UV_TILE\nuniform float tileCountX;\nuniform float tileCountY;\n#endif\n\n#ifdef PIVOT\n// Quad offset in particle-size units: xy in the quad's plane, z toward the camera.\nuniform vec3 pivot;\n#endif\n\n// Varyings\nvarying vec2 vUV;\nvarying vec4 vColor;\n#ifdef TILE_BLEND\nvarying vec2 vUV2;\nvarying float vTileBlend;\n#endif\n#ifdef SOFT_PARTICLES\nvarying vec4 projPosition;\nvarying float linearDepth;\n#endif\n\nvoid main() {\n float c = cos(rotation);\n float s = sin(rotation);\n\n // A negative size mirrors the quad, which is how flipped particles arrive.\n vec2 alignedPosition = position.xy * size.xy;\n#ifdef PIVOT\n // The pivot turns with the particle but is not mirrored with it.\n alignedPosition += pivot.xy * abs(size.xy);\n#endif\n vec2 rotatedPosition;\n rotatedPosition.x = c * alignedPosition.x - s * alignedPosition.y;\n rotatedPosition.y = s * alignedPosition.x + c * alignedPosition.y;\n\n#ifdef HORIZONTAL\n vec4 mvPosition = world * vec4(offset, 1.0);\n mvPosition.x += rotatedPosition.x;\n mvPosition.z -= rotatedPosition.y;\n#ifdef PIVOT\n mvPosition.y += pivot.z * abs(size.x);\n#endif\n mvPosition = view * mvPosition;\n#elif defined(VERTICAL)\n vec4 mvPosition = world * vec4(offset, 1.0);\n mvPosition.y += rotatedPosition.y;\n mvPosition = view * mvPosition;\n mvPosition.x += rotatedPosition.x;\n#else\n vec4 mvPosition = view * world * vec4(offset, 1.0);\n mvPosition.xy += rotatedPosition;\n#endif\n#ifdef PIVOT\n#ifndef HORIZONTAL\n // Toward the camera along its view axis, whichever way the view space\n // faces: anything in front of the camera has the sign of the far side.\n mvPosition.z -= sign(mvPosition.z) * pivot.z * abs(size.x);\n#endif\n#endif\n\n gl_Position = projection * mvPosition;\n#ifdef SOFT_PARTICLES\n projPosition = gl_Position;\n linearDepth = -mvPosition.z;\n#endif\n\n #ifdef UV_TILE\n vec2 tc = vec2(tileCountX, tileCountY);\n float baseTile = floor(uvTile);\n float tileU = mod(baseTile, tc.x) / tc.x;\n float tileV = 1.0 - floor(baseTile / tc.x) / tc.y - 1.0 / tc.y;\n vUV = uv / tc + vec2(tileU, tileV);\n #ifdef TILE_BLEND\n float nextTile = ceil(uvTile);\n float nextU = mod(nextTile, tc.x) / tc.x;\n float nextV = 1.0 - floor(nextTile / tc.x) / tc.y - 1.0 / tc.y;\n vUV2 = uv / tc + vec2(nextU, nextV);\n vTileBlend = fract(uvTile);\n #endif\n #else\n vUV = uv;\n #endif\n\n vColor = color;\n}\n";
3678
4117
 
@@ -3694,6 +4133,10 @@ declare class SpriteBatch extends VFXBatch {
3694
4133
  private velocityBuffer?;
3695
4134
  /** Concretely typed handle on the batch material, see syncStretchedSpeedFactor. */
3696
4135
  private shaderMaterial;
4136
+ /** The `'fixed'` mesh light, as shader uniforms; see applyMeshLighting. */
4137
+ private readonly fixedLightDirection;
4138
+ private readonly fixedLightColor;
4139
+ private readonly fixedAmbientColor;
3697
4140
  private offsetVB;
3698
4141
  private rotationVB;
3699
4142
  private sizeVB;
@@ -3707,6 +4150,18 @@ declare class SpriteBatch extends VFXBatch {
3707
4150
  private buildExpandableBuffers;
3708
4151
  expandBuffers(target: number): void;
3709
4152
  rebuildMaterial(): void;
4153
+ applyMeshLighting(lighting: MeshLightingSettings): void;
4154
+ private storeFixedLighting;
4155
+ private writeFixedLighting;
4156
+ /**
4157
+ * Reads the scene's key light for `'scene'` mesh lighting: the first
4158
+ * enabled directional or hemispheric light, its diffuse colour times its
4159
+ * intensity, plus the scene's ambient colour. A hemispheric light lights
4160
+ * from its `direction` (which points at the sky) and adds its ground colour
4161
+ * to the ambient, which approximates its sky-to-ground blend with the one
4162
+ * directional term the shader has. False when there is no such light.
4163
+ */
4164
+ static readSceneLight(scene: Scene, direction: Vector3$1, color: Vector3$1, ambient: Vector3$1): boolean;
3710
4165
  private vector_;
3711
4166
  private vector2_;
3712
4167
  private vector3_;
@@ -3740,6 +4195,23 @@ declare class SpriteBatch extends VFXBatch {
3740
4195
  * stretched shader reads the sign and mirrors the texture coordinates.
3741
4196
  */
3742
4197
  private static applyFlip;
4198
+ /** The same for a system without a store, read off the particles. */
4199
+ private static applyFlipFromParticles;
4200
+ /**
4201
+ * Writes the mesh rotations for rows `[0, count)` of a store: each
4202
+ * particle's quaternion, carried on by `fraction` of its last turn (see
4203
+ * {@link partialTurn}) and, for a local-space system, turned by the
4204
+ * emitter's rotation `parent`.
4205
+ */
4206
+ private static writeQuaternions;
4207
+ /**
4208
+ * Writes the stretched billboards' velocity attribute for rows `[0, count)`
4209
+ * of a store: the velocity carried on by `fraction` of the last step (see
4210
+ * {@link extrapolate}), turned into world space by `rotation` when the
4211
+ * system simulates locally, scaled by the speed factor, with the length
4212
+ * factor in `w`.
4213
+ */
4214
+ private static writeVelocities;
3743
4215
  /**
3744
4216
  * The fraction `f` of a turn, as a normalised lerp from no rotation toward
3745
4217
  * `step`, written into `out`.
@@ -3803,7 +4275,15 @@ declare class TrailBatch extends VFXBatch {
3803
4275
  private vector2_;
3804
4276
  private vector3_;
3805
4277
  private quaternion_;
3806
- /** Forces the emitter's world matrix to be current before sampling it. */
4278
+ /**
4279
+ * Brings the emitter's world matrix up to date when its transform changed.
4280
+ *
4281
+ * Babylon only recomputes a world matrix once per render id, so a frame that
4282
+ * moves the emitter after that (or a step driven without rendering) would
4283
+ * draw the ribbon against the old one. Recomputing unconditionally every
4284
+ * frame costs a matrix compose per system; checking the transform cache
4285
+ * first makes a still emitter free.
4286
+ */
3807
4287
  private static ensureWorldMatrix;
3808
4288
  update(): void;
3809
4289
  /**
@@ -3827,5 +4307,5 @@ declare class TrailBatch extends VFXBatch {
3827
4307
  dispose(): void;
3828
4308
  }
3829
4309
 
3830
- export { ApplyCollision, ApplyForce, ApplySequences, AxisAngleGenerator, BatchedParticleRenderer, BatchedRenderer, BehaviorFromJSON, BehaviorTypes, Bezier, BoxEmitFrom, BoxEmitter, ChangeEmitDirection, CircleEmitter, ColorBySpeed, ColorGeneratorFromJSON, ColorOverLife, ColorRange, ConeEmitter, ConstantColor, ConstantValue, DEG2RAD, DonutEmitter, EdgeEmitter, EmitSubParticleSystem, EmitterFromJSON, EmitterMode, EmitterShapes, Euler, EulerGenerator, ForceOverLife, FrameOverLife, GeneratorFromJSON, Gradient, GravityForce, GridEmitter, HemisphereEmitter, InheritVelocity, IntervalValue, LimitSpeedOverLife, MathUtils, Matrix3, Matrix4, MeshSurfaceEmitter, MeshSurfaceEmitterPlugin, NodeParticle, Noise, OrbitOverLife, ParticleEmitter, ParticleMeshPhysicsMaterial, ParticleMeshStandardMaterial, ParticleStore, ParticleSystem, PiecewiseBezier, PiecewiseFunction, Plugins, PointEmitter, QuarksLoader, QuarksPrefab, QuarksUtil, Quaternion, RAD2DEG, RandomColor, RandomColorBetweenGradient, RandomQuatGenerator, RecordState, RectangleEmitter, RenderMode, Rotation3DOverLife, RotationBySpeed, RotationGeneratorFromJSON, RotationOverLife, SequencerFromJSON, ShapeTransform, SizeBySpeed, SizeOverLife, SpeedOverLife, SphereEmitter, SpriteBatch, SpriteParticle, SubParticleEmitMode, TextureSequencer, TrailBatch, TrailParticle, TurbulenceField, VFXBatch, ValueGeneratorFromJSON, Vector2, Vector3, Vector3Function, Vector3GeneratorFromJSON, Vector3View, Vector4, Vector4View, VelocityOverLife, WebGLCoordinateSystem, WebGPUCoordinateSystem, WidthOverLength, cacheEnvAtlas, ceilPowerOfTwo, clamp, createEnvAtlasFromFaceUrls, cubeFaceUrls, damp, degToRad, denormalize, ensureEnvAtlasFromCube, euclideanModulo, floorPowerOfTwo, generateUUID, getCachedEnvAtlas, getPhysicsResolver, getRegisteredShaderChunks, getShaderChunk, getValueFromEmitterMode, inverseLerp, isPowerOfTwo, lerp, loadPlugin, _default$7 as localParticlePhysicsVertShader, _default$6 as localParticleVertShader, mapLinear, normalize, _default$5 as particleFragShader, _default$4 as particlePhysicsFragShader, _default$3 as particleVertShader, pingpong, planBehaviorFusion, radToDeg, randFloat, randFloatSpread, randInt, registerShaderChunks, seededRandom, setPhysicsResolver, setQuaternionFromProperEuler, smootherstep, smoothstep, _default$2 as stretchedBillboardVertShader, _default$1 as trailFragShader, _default as trailVertShader, unloadPlugin };
3831
- export type { AdaptivePerformanceOptions, AdaptivePerformanceState, AnimationData, BabylonMetaData, BabylonParticleMaterialParameters, Behavior, BehaviorPlugin, BillBoardSettings, BoxEmitterParameters, BurstParameters, BurstParametersJSON, CircleEmitterParameters, ColorGenerator, ConeEmitterParameters, Constructable, DonutEmitterParameters, EdgeEmitterParameters, EmissionState, EmitterShape, EmitterShapePlugin, EulerOrder, FieldType, FunctionColorGenerator, FunctionJSON, FunctionValueGenerator, FusedPass, FusionStep, GeneratorMemory, GridEmitterParameters, HemisphereEmitterParameters, IAnimationData, IEmitter, IParticle, IParticleSystem, IPrefab, InheritVelocityMode, JsonMetaData, MeshSettings, ParameterPair, ParameterType, Particle, ParticleSystemEvent, ParticleSystemEventType, ParticleSystemJSONParameters, ParticleSystemParameters, Plugin, QuarksLoaderOptions, QuarksTimelineClip, RectangleEmitterParameters, RendererEmitterSettings, Resource, RotationGenerator, Sequencer, SerializationOptions, ShapeJSON, ShapeTransformJSON, SphereEmitterParameters, StoredBatchSettings, StretchedBillBoardSettings, TrailSettings, VFXBatchSettings, ValueGenerator, Vector3Generator, VelocityOverLifeSpace };
4310
+ export { ApplyCollision, ApplyForce, ApplySequences, AxisAngleGenerator, BatchedParticleRenderer, BatchedRenderer, BehaviorFromJSON, BehaviorTypes, Bezier, BoxEmitFrom, BoxEmitter, ChangeEmitDirection, CircleEmitter, ColorBySpeed, ColorGeneratorFromJSON, ColorOverLife, ColorRange, ConeEmitter, ConstantColor, ConstantValue, DEG2RAD, DonutEmitter, EdgeEmitter, EmitSubParticleSystem, EmitterFromJSON, EmitterMode, EmitterShapes, Euler, EulerGenerator, ForceOverLife, FrameOverLife, GeneratorFromJSON, Gradient, GravityForce, GridEmitter, HemisphereEmitter, InheritVelocity, IntervalValue, LimitSpeedOverLife, MathUtils, Matrix3, Matrix4, MeshSurfaceEmitter, MeshSurfaceEmitterPlugin, NodeParticle, Noise, OrbitOverLife, ParticleEmitter, ParticleMeshPhysicsMaterial, ParticleMeshStandardMaterial, ParticleStore, ParticleSystem, PiecewiseBezier, PiecewiseFunction, Plugins, PointEmitter, QuarksLoader, QuarksPrefab, QuarksUtil, Quaternion, QuaternionView, RAD2DEG, RandomColor, RandomColorBetweenGradient, RandomQuatGenerator, RecordState, RectangleEmitter, RenderMode, Rotation3DOverLife, RotationBySpeed, RotationGeneratorFromJSON, RotationOverLife, SequencerFromJSON, ShapeTransform, SizeBySpeed, SizeOverLife, SpeedOverLife, SphereEmitter, SpriteBatch, SpriteParticle, SubParticleEmitMode, TextureSequencer, TrailBatch, TrailParticle, TurbulenceField, VFXBatch, ValueGeneratorFromJSON, Vector2, Vector3, Vector3Function, Vector3GeneratorFromJSON, Vector3View, Vector4, Vector4View, VelocityOverLife, WebGLCoordinateSystem, WebGPUCoordinateSystem, WidthOverLength, cacheEnvAtlas, ceilPowerOfTwo, clamp, createEnvAtlasFromFaceUrls, cubeFaceUrls, damp, defaultMeshLighting, degToRad, denormalize, ensureEnvAtlasFromCube, euclideanModulo, floorPowerOfTwo, generateUUID, getCachedEnvAtlas, getPhysicsResolver, getRegisteredShaderChunks, getShaderChunk, getValueFromEmitterMode, inverseLerp, isPowerOfTwo, lerp, loadPlugin, _default$7 as localParticlePhysicsVertShader, _default$6 as localParticleVertShader, mapLinear, meshRimEqual, normalize, normalizeMeshRim, _default$5 as particleFragShader, _default$4 as particlePhysicsFragShader, _default$3 as particleVertShader, pingpong, planBehaviorFusion, radToDeg, randFloat, randFloatSpread, randInt, registerShaderChunks, seededRandom, setPhysicsResolver, setQuaternionFromProperEuler, smootherstep, smoothstep, _default$2 as stretchedBillboardVertShader, _default$1 as trailFragShader, _default as trailVertShader, unloadPlugin };
4311
+ export type { AdaptivePerformanceOptions, AdaptivePerformanceState, AnimationData, BabylonMetaData, BabylonParticleMaterialParameters, Behavior, BehaviorPlugin, BillBoardSettings, BoxEmitterParameters, BurstParameters, BurstParametersJSON, CircleEmitterParameters, ColorGenerator, ConeEmitterParameters, Constructable, DonutEmitterParameters, EdgeEmitterParameters, EmissionState, EmitterShape, EmitterShapePlugin, EulerOrder, FieldType, FunctionColorGenerator, FunctionJSON, FunctionValueGenerator, FusedPass, FusionStep, GeneratorMemory, GridEmitterParameters, HemisphereEmitterParameters, IAnimationData, IEmitter, IParticle, IParticleSystem, IPrefab, InheritVelocityMode, JsonMetaData, MeshLighting, MeshLightingSettings, MeshRim, MeshSettings, ParameterPair, ParameterType, Particle, ParticleSystemEvent, ParticleSystemEventType, ParticleSystemJSONParameters, ParticleSystemParameters, Plugin, QuarksBatchLoadOptions, QuarksJSONLoadEntry, QuarksLoaderOptions, QuarksResourceKind, QuarksResourceResolver, QuarksTimelineClip, RectangleEmitterParameters, RendererEmitterSettings, Resource, RotationGenerator, Sequencer, SerializationOptions, ShapeJSON, ShapeTransformJSON, SphereEmitterParameters, StoredBatchSettings, StretchedBillBoardSettings, TrailSettings, VFXBatchSettings, ValueGenerator, Vector3Generator, VelocityOverLifeSpace };