babylon.quarks 0.21.0 → 0.24.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 (36) hide show
  1. package/CHANGELOG.md +184 -0
  2. package/dist/babylon.quarks.cjs +1 -1
  3. package/dist/babylon.quarks.cjs.map +1 -1
  4. package/dist/babylon.quarks.esm.js +1 -1
  5. package/dist/babylon.quarks.esm.js.map +1 -1
  6. package/dist/babylon.quarks.umd.min.js +1 -1
  7. package/dist/babylon.quarks.umd.min.js.map +1 -1
  8. package/dist/types/index.d.ts +936 -45
  9. package/package.json +2 -2
  10. package/src/BatchedRenderer.ts +205 -4
  11. package/src/EffectPool.ts +165 -0
  12. package/src/MeshSurfaceEmitter.ts +4 -4
  13. package/src/ParticleEmitter.ts +2 -1
  14. package/src/ParticleSystem.ts +975 -69
  15. package/src/QuarksLoader.ts +25 -4
  16. package/src/QuarksUtil.ts +55 -7
  17. package/src/SpriteBatch.ts +515 -114
  18. package/src/TrailBatch.ts +128 -75
  19. package/src/VFXBatch.ts +255 -3
  20. package/src/index.ts +5 -2
  21. package/src/instanceSort.ts +121 -0
  22. package/src/shaders/chunks/fog.ts +127 -0
  23. package/src/shaders/local_particle_physics_vert.glsl.ts +4 -0
  24. package/src/shaders/local_particle_physics_vert.wgsl.ts +4 -0
  25. package/src/shaders/particle_frag.glsl.ts +4 -0
  26. package/src/shaders/particle_frag.wgsl.ts +4 -0
  27. package/src/shaders/particle_physics_frag.glsl.ts +30 -1
  28. package/src/shaders/particle_physics_frag.wgsl.ts +30 -1
  29. package/src/shaders/particle_vert.glsl.ts +23 -4
  30. package/src/shaders/particle_vert.wgsl.ts +23 -4
  31. package/src/shaders/stretched_bb_particle_vert.glsl.ts +17 -0
  32. package/src/shaders/stretched_bb_particle_vert.wgsl.ts +18 -1
  33. package/src/shaders/trail_frag.glsl.ts +4 -0
  34. package/src/shaders/trail_frag.wgsl.ts +4 -0
  35. package/src/shaders/trail_vert.glsl.ts +4 -0
  36. package/src/shaders/trail_vert.wgsl.ts +4 -0
package/CHANGELOG.md CHANGED
@@ -4,6 +4,190 @@ All notable changes to the `babylon.quarks` package are documented here.
4
4
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
5
5
  and the project follows the `quarks.core` 0.x version line.
6
6
 
7
+ ## [0.24.0] — 2026-10-20
8
+
9
+ ### Added
10
+
11
+ - `ParticleSystem.renderingGroupId` (constructor option, JSON `renderingGroupId`, written only when
12
+ not 0): the Babylon rendering group the batch mesh draws in. Systems with different groups are
13
+ batched apart.
14
+ - `ParticleSystem.applyFog` (constructor option, JSON `applyFog`, written only when false): the
15
+ scene's fog now reaches the particles in every render mode, in GLSL and WGSL, the way it reaches
16
+ any other mesh. Additive blending fades toward black instead of the fog colour. Fog is bound from
17
+ the scene on every draw, so enabling or disabling it at runtime needs no recompile.
18
+ - `ParticleSystem.playbackSpeed` (constructor option, JSON `playbackSpeed`, written only when
19
+ not 1): a multiplier on the time the system is advanced by — Unity's Simulation Speed. The
20
+ Unity exporter writes it from Main → Simulation Speed.
21
+ - quarks.core `ApplySequences` is registered in `BehaviorTypes`, so an effect using it serialises
22
+ and loads like any other behavior.
23
+ - Bounds and culling. Every system measures the world bounds of what it draws
24
+ (`ParticleSystem.getWorldBounds`), from the store columns for sprites and meshes and from the
25
+ ribbons for trails; batch meshes carry the union, so Babylon's frustum culling skips the draw of
26
+ a batch no camera sees and transparent sorting has real bounds to work with. Measured in full
27
+ every few steps and carried, padded for the motion since, in between: no measurable cost on the
28
+ profiling scenarios.
29
+ - `ParticleSystem.cullingMode` (constructor option, JSON `cullingMode`, written only when not
30
+ `'always'`): Unity's Culling Mode. `'pause'` freezes a system while no active camera sees its
31
+ bounds; `'pauseAndCatchUp'` also replays the missed time (up to 2 s) when it comes back, within
32
+ `BatchedRenderer.catchUpBudgetMs` (2 ms per frame, shared by all systems; what does not fit is
33
+ dropped). Culled systems are neither simulated nor filled into their batch. Empty systems and sub
34
+ emitter targets are never culled; `BatchedRenderer.cullingEnabled = false` turns the test off.
35
+ With 400 systems in a ring around the camera and three quarters of them out of view, a frame goes
36
+ from 9.6 ms to 1.6 ms. The Unity exporter writes Pause and Pause And Catch-up.
37
+ - Deterministic randomness. Every roll the simulation makes now goes through quarks.core's
38
+ `random()`, whose source is replaceable: `setRandomSource(createSeededRandom(seed))` makes a whole
39
+ scene reproducible, `withRandomSource` scopes it, and `getRandomSource` reads it back. The noise
40
+ generators are seeded by slot, so a Noise behavior looks the same on every run. UUIDs keep using
41
+ `Math.random`.
42
+ - `ParticleSystem.seed` (constructor option, JSON `seed`, written only when set): the system rolls
43
+ on its own generator started from the seed while it steps, and restarts it on `restart()`, so the
44
+ effect plays out identically every time; systems it emits into roll on it too. The editor has a
45
+ "Fixed seed" field; the Unity exporter writes Main → Random Seed when Auto Random Seed is off.
46
+ - `EffectPool`: a pool of copies of one effect for things played many times over. `acquire(position,
47
+ parent)` takes an idle copy (or makes one, up to `maxSize`), places it and plays it; a copy whose
48
+ systems have all finished returns to the pool by itself; `stop(copy)` ends a looping copy's
49
+ emission and lets it die out first; `release(copy)` takes it back at once; `prewarm` makes copies
50
+ up front. Idle copies stay disabled in the renderer's batches, so acquiring allocates nothing.
51
+ - `QuarksUtil.instantiate(root)`: a deep copy of an effect tree, with sub emitter links pointing at
52
+ the copied emitters.
53
+ - `ParticleSystem.sortMode` (constructor option, JSON `sortMode`, written only when not `'none'`):
54
+ Unity's Sort Mode. `'distance'` draws the particles of the batch farthest from the active camera
55
+ first, so nearer alpha-blended particles cover farther ones; `'oldestInFront'` and
56
+ `'youngestInFront'` order by age. The order is a radix sort over the whole batch after each fill
57
+ (about 2.5 ms per 50k particles), so systems with different modes are batched apart; trails are
58
+ not sorted. The editor's Renderer module and the Unity exporter carry it (Distance and Depth both
59
+ map to `'distance'`).
60
+ - `ParticleSystem.minParticleSize` / `maxParticleSize` (constructor options, JSON, written only when
61
+ set): Unity's Min / Max Particle Size, the smallest and largest size a billboard or stretched
62
+ particle is drawn at as a fraction of the viewport height, clamped in the vertex shader (GLSL and
63
+ WGSL). Meshes and trails ignore them. The editor's Renderer module has them under "Limit screen
64
+ size"; the exporter writes Unity's values, its default maximum of 0.5 included.
65
+ - `ParticleSystem.alignToDirection` (constructor option, JSON `alignToDirection`, written only when
66
+ true): Unity's Align To Direction. A mesh particle's +Z is turned along the direction its shape
67
+ sends it; a billboard's top is turned toward it in the emitter's XY plane. The start rotation is
68
+ applied on top; a particle with no speed is left alone. Shape module checkbox in the editor; the
69
+ exporter writes Shape → Align To Direction.
70
+ - quarks.core `Noise` gains `scrollSpeed` (Unity's Scroll Speed: the field moves past the particles,
71
+ sampled once per frame, reset with the behavior) and `remap` (Unity's Remap: a curve read at the
72
+ raw noise mapped from [-1, 1] to [0, 1], whose value replaces it). Both in JSON, the editor's Noise
73
+ module and the exporter.
74
+ - quarks.core `VelocityOverLife` gains `radial` (Unity's Radial: speed away from the orbit centre)
75
+ and `orbitalOffset` (Unity's Orbital Offset: where the orbital speeds turn around, relative to
76
+ the emitter). In JSON, the editor's Velocity module and the exporter.
77
+ - quarks.core `EmitSubParticleSystem` gains `inherit` (Unity's sub emitter Inherit): `'color'` and
78
+ `'size'` multiply the sub particles' start values by the parent's current ones, `'rotation'` adds
79
+ the parent's, `'lifetime'` scales theirs by the parent's remaining lifetime fraction. The mask
80
+ travels in the emission state, so a host that emits into a system by hand can set it too. In
81
+ JSON, the editor's Sub Emitters module (four checkboxes) and the exporter.
82
+ - `BatchedRenderer.simulationBudgetMs` (unlimited by default): once a frame's simulation has taken
83
+ this long, the systems still to be advanced run one step instead of catching up on the long
84
+ frame, and the time the skipped steps would have covered is dropped — so a frame that already ran
85
+ long cannot make the next one longer still.
86
+
87
+ ### Changed
88
+
89
+ - Developed and tested against `@babylonjs/core` 9.31.0 (was 9.6.2); the peer range stays
90
+ `>=9.0.0`. The examples and the editor's dev dependencies follow.
91
+ - `ParticleSystem.isFinished()` is true for a looping system whose emission was ended with
92
+ `endEmit()` once its particles are gone, and for a sub emitter target whenever it is empty,
93
+ looping or not; `finished` fires accordingly. `QuarksUtil.isEffectFinished` follows, so an effect
94
+ with looping systems is finished after `endEmit` rather than never.
95
+
96
+ ### Fixed
97
+
98
+ - quarks.core `ApplySequences` threw on update while it had no sequences yet (an editor adds the
99
+ behavior before its first sequence); it now leaves the particles alone until one is appended.
100
+ - `ParticleSystem.clone()` left cloned `EmitSubParticleSystem` behaviors transforming by the
101
+ original system's emitter; they now follow the clone. `ParticleEmitter.clone()` no longer
102
+ disables the clone itself when only an ancestor of the original was disabled.
103
+ - `renderOrder` now reaches the batch mesh as `alphaIndex`, so it orders the batch among the
104
+ scene's transparent meshes instead of only keying the batching; it used to have no effect on
105
+ what was drawn first. `layerMask` likewise now reaches the batch mesh — cameras with a
106
+ non-matching mask used to draw the particles anyway.
107
+
108
+ ## [0.23.0] — 2026-10-18
109
+
110
+ ### Changed
111
+
112
+ - CPU simulation and batch upload rewritten to work on the `ParticleStore` columns instead of
113
+ walking particle objects. Scalar attributes (`rotation`, `angularVelocity`, `uvTile`, `flip`,
114
+ `startSpeed`) and mesh quaternion rotations now live in store columns; the sprite batch copies
115
+ them as ranges and only visits particle objects for sub-emitted systems. Compiled behavior passes
116
+ (now built for single behaviors too) read and write the columns, interpolate sampled curves and
117
+ gradients inline and take per-particle random samples from a mirrored memory column, so a step
118
+ no longer touches the particle object for the built-in behaviors. Snapshots of size and colour
119
+ for the renderer are only kept when a behavior can change them, and the step's integration,
120
+ newborn fix-up and compaction run in one pass. Trail ribbons transform each recorded sample
121
+ once, upload indices straight to the GPU buffer (no per-frame sub mesh rebuild) and recompute
122
+ the emitter matrix only when its transform changed. `Noise` and `TurbulenceField` evaluate
123
+ constant generators once per frame and update through the columns. Measured on 50k particles:
124
+ billboards 2.1–2.9x faster per frame, trails 1.5x, noise/turbulence 1.2x.
125
+ - Assigning a `Quaternion` to a sprite particle's `rotation` or `angularVelocity` copies it into
126
+ the particle's store row; reading the property back returns a view of that row rather than the
127
+ assigned object. `RandomQuatGenerator` stores its sample as four numbers in the particle's
128
+ memory instead of a `Quaternion`. `TrailParticle.previous` (deprecated) is created on first use.
129
+
130
+ ## [0.22.0] — 2026-10-17
131
+
132
+ ### Added
133
+
134
+ - Mesh particle lighting modes: `ParticleSystem.lighting` (constructor option `lighting`, saved
135
+ as `lighting` on the effect's `QuarksMaterial` and read back by `QuarksLoader`) is `'fixed'`
136
+ (default — exactly the light mesh particles had before), `'unlit'` (the lighting term is
137
+ compiled out with `MESH_UNLIT`: texture × particle colour, for emissive meshes) or `'scene'`
138
+ (the scene's first enabled `DirectionalLight` / `HemisphericLight`, re-read every frame:
139
+ direction, diffuse × intensity, `scene.ambientColor`, plus a hemispheric light's ground colour
140
+ in the ambient; falls back to the fixed light when the scene has neither). Systems with
141
+ different lighting are batched apart. The default is not written to JSON.
142
+ - `BatchedRenderer.setMeshLighting({direction, color, ambient})` (Babylon `Vector3` / `Color3`,
143
+ any subset) sets the `'fixed'` light for every mesh batch of the renderer, including later ones;
144
+ `getMeshLighting()` returns a copy, `defaultMeshLighting()` the built-in values
145
+ (`(0.4, -1, 0.6)`, white, `0.35` grey). Exposed in the editor's Renderer module as "Lighting".
146
+ - Mesh particle rim (Fresnel) term: `ParticleSystem.rim` (constructor option `rim`, saved as
147
+ `rim` on the effect's `QuarksMaterial`) — `{color: [r, g, b], power, strength, mode}` with
148
+ defaults `[1, 1, 1]`, `2`, `1`, `'add'`; any field may be left out. The edge term is
149
+ `pow(1 - |N·V|, power) * strength`; `'add'` adds `color` (glowing edge), `'mix'` blends toward it
150
+ (outline). Compiled in only when set (`MESH_RIM` / `MESH_RIM_MIX`), batched by value, written to
151
+ JSON only when set, and exposed in the editor's Renderer module. `normalizeMeshRim` /
152
+ `meshRimEqual` are exported.
153
+
154
+ ### Changed
155
+
156
+ - **Behaviour change:** quarks.core `WidthOverLength` (Width over Trail) now reads its curve as a
157
+ multiplier on the width each trail sample was recorded with (the particle's `size.x` at that
158
+ moment), the way Unity's Width over Trail works, and gives the head sample the curve's value at
159
+ 0, so a trail no longer flares out behind its head or steps at the tip. A new `absolute` flag
160
+ (`new WidthOverLength(curve, absolute = false)`, JSON field `absolute`) keeps the old reading —
161
+ the curve as the width in world units, the head at the particle's size. `toJSON` always writes
162
+ the flag; JSON without it (everything written by 0.21 and earlier, including the Unity
163
+ exporter's output) loads as `absolute: true`, so authored effects keep their look. Code that
164
+ builds `new WidthOverLength(curve)` gets the new multiplier behaviour: divide an absolute curve
165
+ by the start size, or pass `true`. `TrailParticle` gains `historyBaseSizes` (the unscaled
166
+ recorded widths) and `headWidthScale`.
167
+
168
+ ### Fixed
169
+
170
+ - Burst `cycle` / `interval` are honoured: a burst fires `cycle` waves, `interval` seconds apart,
171
+ each rolling `probability` on its own. Repeats past the system's `duration` are dropped (a
172
+ looping system starts them over every loop), a `cycle` below 1 fires once, and a zero interval
173
+ fires every wave at once. Previously every burst fired a single wave.
174
+ - Emission over distance spreads the particles it owes along the path the emitter travelled
175
+ this step, each at its own metre mark (the spacing carries across steps), instead of spawning
176
+ all of them at the emitter's current position — a fast world-space emitter now leaves a
177
+ continuous trail rather than one clump per frame. Only the position is interpolated (rotation
178
+ and scale are the emitter's current ones) and the particles start at age 0. Local-space
179
+ systems, whose particles move with the emitter, are unchanged.
180
+ - A sub emitter whose target uuid is not in the effect no longer throws on every frame.
181
+ `QuarksLoader` unlinks it (`subParticleSystem = undefined`) with one `console.warn` naming the
182
+ emitter and the uuid — also when the uuid names a node that is not a particle emitter — and
183
+ quarks.core `EmitSubParticleSystem` skips any value that is not a resolved emitter. An
184
+ unresolved uuid string is written back unchanged by `toJSON`.
185
+ - quarks.core `RotationOverLife` turns mesh particles too: a quaternion rotation is turned about
186
+ the particle's local Z axis (the axis a number `startRotation` uses, and what Unity's Rotation
187
+ over Lifetime without separate axes does to meshes), in its own pass and in the fused pass, and
188
+ the step is kept for the renderer's in-between-step interpolation. It used to do nothing on
189
+ meshes, silently. `Rotation3DOverLife` remains the behavior for any other axis.
190
+
7
191
  ## [0.21.0] — 2026-10-16
8
192
 
9
193
  ### Added