@codexo/exojs 0.7.12 → 0.8.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 (219) hide show
  1. package/CHANGELOG.md +737 -0
  2. package/dist/esm/core/Application.d.ts +3 -1
  3. package/dist/esm/core/Application.js +7 -6
  4. package/dist/esm/core/Application.js.map +1 -1
  5. package/dist/esm/core/Scene.d.ts +30 -0
  6. package/dist/esm/core/Scene.js +56 -0
  7. package/dist/esm/core/Scene.js.map +1 -1
  8. package/dist/esm/core/SceneManager.js +2 -2
  9. package/dist/esm/core/SceneManager.js.map +1 -1
  10. package/dist/esm/debug/DebugOverlay.js +2 -2
  11. package/dist/esm/debug/DebugOverlay.js.map +1 -1
  12. package/dist/esm/debug/PointerStackLayer.js +1 -1
  13. package/dist/esm/debug/PointerStackLayer.js.map +1 -1
  14. package/dist/esm/index.js +32 -10
  15. package/dist/esm/index.js.map +1 -1
  16. package/dist/esm/input/ArcadeStickGamepadMapping.js +18 -19
  17. package/dist/esm/input/ArcadeStickGamepadMapping.js.map +1 -1
  18. package/dist/esm/input/Gamepad.d.ts +164 -62
  19. package/dist/esm/input/Gamepad.js +290 -134
  20. package/dist/esm/input/Gamepad.js.map +1 -1
  21. package/dist/esm/input/GamepadAxis.d.ts +120 -0
  22. package/dist/esm/input/GamepadAxis.js +106 -0
  23. package/dist/esm/input/GamepadAxis.js.map +1 -0
  24. package/dist/esm/input/GamepadButton.d.ts +110 -0
  25. package/dist/esm/input/GamepadButton.js +99 -0
  26. package/dist/esm/input/GamepadButton.js.map +1 -0
  27. package/dist/esm/input/GamepadDefinitions.js +4 -0
  28. package/dist/esm/input/GamepadDefinitions.js.map +1 -1
  29. package/dist/esm/input/GamepadMapping.d.ts +28 -24
  30. package/dist/esm/input/GamepadMapping.js +33 -16
  31. package/dist/esm/input/GamepadMapping.js.map +1 -1
  32. package/dist/esm/input/GamepadPromptLayouts.d.ts +10 -8
  33. package/dist/esm/input/GamepadPromptLayouts.js +21 -20
  34. package/dist/esm/input/GamepadPromptLayouts.js.map +1 -1
  35. package/dist/esm/input/GenericDualAnalogGamepadMapping.d.ts +6 -3
  36. package/dist/esm/input/GenericDualAnalogGamepadMapping.js +55 -46
  37. package/dist/esm/input/GenericDualAnalogGamepadMapping.js.map +1 -1
  38. package/dist/esm/input/InputBinding.d.ts +74 -0
  39. package/dist/esm/input/InputBinding.js +100 -0
  40. package/dist/esm/input/InputBinding.js.map +1 -0
  41. package/dist/esm/input/InputManager.d.ts +79 -33
  42. package/dist/esm/input/InputManager.js +229 -104
  43. package/dist/esm/input/InputManager.js.map +1 -1
  44. package/dist/esm/input/InteractionManager.d.ts +1 -1
  45. package/dist/esm/input/InteractionManager.js +13 -13
  46. package/dist/esm/input/InteractionManager.js.map +1 -1
  47. package/dist/esm/input/JoyConLeftGamepadMapping.d.ts +14 -9
  48. package/dist/esm/input/JoyConLeftGamepadMapping.js +39 -9
  49. package/dist/esm/input/JoyConLeftGamepadMapping.js.map +1 -1
  50. package/dist/esm/input/JoyConRightGamepadMapping.d.ts +14 -9
  51. package/dist/esm/input/JoyConRightGamepadMapping.js +35 -9
  52. package/dist/esm/input/JoyConRightGamepadMapping.js.map +1 -1
  53. package/dist/esm/input/Pointer.d.ts +84 -71
  54. package/dist/esm/input/Pointer.js +71 -71
  55. package/dist/esm/input/Pointer.js.map +1 -1
  56. package/dist/esm/input/SteamDeckGamepadMapping.d.ts +18 -0
  57. package/dist/esm/input/SteamDeckGamepadMapping.js +76 -0
  58. package/dist/esm/input/SteamDeckGamepadMapping.js.map +1 -0
  59. package/dist/esm/input/index.d.ts +7 -4
  60. package/dist/esm/input/types.d.ts +0 -76
  61. package/dist/esm/input/types.js +1 -80
  62. package/dist/esm/input/types.js.map +1 -1
  63. package/dist/esm/particles/ParticleSystem.d.ts +180 -83
  64. package/dist/esm/particles/ParticleSystem.js +446 -133
  65. package/dist/esm/particles/ParticleSystem.js.map +1 -1
  66. package/dist/esm/particles/distributions/BoxArea.d.ts +17 -0
  67. package/dist/esm/particles/distributions/BoxArea.js +48 -0
  68. package/dist/esm/particles/distributions/BoxArea.js.map +1 -0
  69. package/dist/esm/particles/distributions/CircleArea.d.ts +19 -0
  70. package/dist/esm/particles/distributions/CircleArea.js +33 -0
  71. package/dist/esm/particles/distributions/CircleArea.js.map +1 -0
  72. package/dist/esm/particles/distributions/ConeDirection.d.ts +28 -0
  73. package/dist/esm/particles/distributions/ConeDirection.js +44 -0
  74. package/dist/esm/particles/distributions/ConeDirection.js.map +1 -0
  75. package/dist/esm/particles/distributions/Constant.d.ts +17 -0
  76. package/dist/esm/particles/distributions/Constant.js +35 -0
  77. package/dist/esm/particles/distributions/Constant.js.map +1 -0
  78. package/dist/esm/particles/distributions/Curve.d.ts +30 -0
  79. package/dist/esm/particles/distributions/Curve.js +53 -0
  80. package/dist/esm/particles/distributions/Curve.js.map +1 -0
  81. package/dist/esm/particles/distributions/Distribution.d.ts +45 -0
  82. package/dist/esm/particles/distributions/Gradient.d.ts +40 -0
  83. package/dist/esm/particles/distributions/Gradient.js +72 -0
  84. package/dist/esm/particles/distributions/Gradient.js.map +1 -0
  85. package/dist/esm/particles/distributions/LineSegment.d.ts +15 -0
  86. package/dist/esm/particles/distributions/LineSegment.js +27 -0
  87. package/dist/esm/particles/distributions/LineSegment.js.map +1 -0
  88. package/dist/esm/particles/distributions/Range.d.ts +12 -0
  89. package/dist/esm/particles/distributions/Range.js +19 -0
  90. package/dist/esm/particles/distributions/Range.js.map +1 -0
  91. package/dist/esm/particles/distributions/VectorRange.d.ts +20 -0
  92. package/dist/esm/particles/distributions/VectorRange.js +31 -0
  93. package/dist/esm/particles/distributions/VectorRange.js.map +1 -0
  94. package/dist/esm/particles/distributions/index.d.ts +12 -0
  95. package/dist/esm/particles/gpu/ParticleGpuState.d.ts +57 -0
  96. package/dist/esm/particles/gpu/ParticleGpuState.js +508 -0
  97. package/dist/esm/particles/gpu/ParticleGpuState.js.map +1 -0
  98. package/dist/esm/particles/index.d.ts +2 -10
  99. package/dist/esm/particles/modules/AlphaFadeOverLifetime.d.ts +24 -0
  100. package/dist/esm/particles/modules/AlphaFadeOverLifetime.js +60 -0
  101. package/dist/esm/particles/modules/AlphaFadeOverLifetime.js.map +1 -0
  102. package/dist/esm/particles/modules/ApplyForce.d.ts +20 -0
  103. package/dist/esm/particles/modules/ApplyForce.js +48 -0
  104. package/dist/esm/particles/modules/ApplyForce.js.map +1 -0
  105. package/dist/esm/particles/modules/AttractToPoint.d.ts +27 -0
  106. package/dist/esm/particles/modules/AttractToPoint.js +73 -0
  107. package/dist/esm/particles/modules/AttractToPoint.js.map +1 -0
  108. package/dist/esm/particles/modules/BurstSpawn.d.ts +53 -0
  109. package/dist/esm/particles/modules/BurstSpawn.js +94 -0
  110. package/dist/esm/particles/modules/BurstSpawn.js.map +1 -0
  111. package/dist/esm/particles/modules/ColorOverLifetime.d.ts +22 -0
  112. package/dist/esm/particles/modules/ColorOverLifetime.js +65 -0
  113. package/dist/esm/particles/modules/ColorOverLifetime.js.map +1 -0
  114. package/dist/esm/particles/modules/ColorOverSpeed.d.ts +27 -0
  115. package/dist/esm/particles/modules/ColorOverSpeed.js +86 -0
  116. package/dist/esm/particles/modules/ColorOverSpeed.js.map +1 -0
  117. package/dist/esm/particles/modules/DeathModule.d.ts +24 -0
  118. package/dist/esm/particles/modules/DeathModule.js +25 -0
  119. package/dist/esm/particles/modules/DeathModule.js.map +1 -0
  120. package/dist/esm/particles/modules/Drag.d.ts +20 -0
  121. package/dist/esm/particles/modules/Drag.js +45 -0
  122. package/dist/esm/particles/modules/Drag.js.map +1 -0
  123. package/dist/esm/particles/modules/OrbitalForce.d.ts +28 -0
  124. package/dist/esm/particles/modules/OrbitalForce.js +65 -0
  125. package/dist/esm/particles/modules/OrbitalForce.js.map +1 -0
  126. package/dist/esm/particles/modules/RateSpawn.d.ts +41 -0
  127. package/dist/esm/particles/modules/RateSpawn.js +76 -0
  128. package/dist/esm/particles/modules/RateSpawn.js.map +1 -0
  129. package/dist/esm/particles/modules/RepelFromPoint.d.ts +24 -0
  130. package/dist/esm/particles/modules/RepelFromPoint.js +76 -0
  131. package/dist/esm/particles/modules/RepelFromPoint.js.map +1 -0
  132. package/dist/esm/particles/modules/RotateOverLifetime.d.ts +20 -0
  133. package/dist/esm/particles/modules/RotateOverLifetime.js +43 -0
  134. package/dist/esm/particles/modules/RotateOverLifetime.js.map +1 -0
  135. package/dist/esm/particles/modules/ScaleOverLifetime.d.ts +26 -0
  136. package/dist/esm/particles/modules/ScaleOverLifetime.js +59 -0
  137. package/dist/esm/particles/modules/ScaleOverLifetime.js.map +1 -0
  138. package/dist/esm/particles/modules/SpawnModule.d.ts +30 -0
  139. package/dist/esm/particles/modules/SpawnModule.js +31 -0
  140. package/dist/esm/particles/modules/SpawnModule.js.map +1 -0
  141. package/dist/esm/particles/modules/SpawnOnDeath.d.ts +24 -0
  142. package/dist/esm/particles/modules/SpawnOnDeath.js +47 -0
  143. package/dist/esm/particles/modules/SpawnOnDeath.js.map +1 -0
  144. package/dist/esm/particles/modules/Turbulence.d.ts +30 -0
  145. package/dist/esm/particles/modules/Turbulence.js +122 -0
  146. package/dist/esm/particles/modules/Turbulence.js.map +1 -0
  147. package/dist/esm/particles/modules/UpdateModule.d.ts +95 -0
  148. package/dist/esm/particles/modules/UpdateModule.js +66 -0
  149. package/dist/esm/particles/modules/UpdateModule.js.map +1 -0
  150. package/dist/esm/particles/modules/VelocityOverLifetime.d.ts +30 -0
  151. package/dist/esm/particles/modules/VelocityOverLifetime.js +84 -0
  152. package/dist/esm/particles/modules/VelocityOverLifetime.js.map +1 -0
  153. package/dist/esm/particles/modules/WgslContribution.d.ts +81 -0
  154. package/dist/esm/particles/modules/WgslContribution.js +34 -0
  155. package/dist/esm/particles/modules/WgslContribution.js.map +1 -0
  156. package/dist/esm/particles/modules/index.d.ts +22 -0
  157. package/dist/esm/rendering/webgl2/WebGl2ParticleRenderer.d.ts +9 -14
  158. package/dist/esm/rendering/webgl2/WebGl2ParticleRenderer.js +90 -61
  159. package/dist/esm/rendering/webgl2/WebGl2ParticleRenderer.js.map +1 -1
  160. package/dist/esm/rendering/webgl2/glsl/particle.vert.js +1 -1
  161. package/dist/esm/rendering/webgpu/WebGpuParticleRenderer.d.ts +9 -0
  162. package/dist/esm/rendering/webgpu/WebGpuParticleRenderer.js +107 -23
  163. package/dist/esm/rendering/webgpu/WebGpuParticleRenderer.js.map +1 -1
  164. package/dist/esm/rendering/webgpu/compute/WebGpuComputePipeline.d.ts +52 -0
  165. package/dist/esm/rendering/webgpu/compute/WebGpuStorageBuffer.d.ts +29 -0
  166. package/dist/esm/rendering/webgpu/compute/index.d.ts +3 -0
  167. package/dist/esm/resources/CacheFirstStrategy.d.ts +7 -4
  168. package/dist/esm/resources/CacheFirstStrategy.js +11 -8
  169. package/dist/esm/resources/CacheFirstStrategy.js.map +1 -1
  170. package/dist/esm/resources/CacheStrategy.d.ts +14 -6
  171. package/dist/esm/resources/Loader.d.ts +8 -3
  172. package/dist/esm/resources/Loader.js +19 -37
  173. package/dist/esm/resources/Loader.js.map +1 -1
  174. package/dist/esm/resources/NetworkOnlyStrategy.d.ts +3 -0
  175. package/dist/esm/resources/NetworkOnlyStrategy.js +8 -3
  176. package/dist/esm/resources/NetworkOnlyStrategy.js.map +1 -1
  177. package/dist/esm/resources/factories/ImageFactory.d.ts +2 -2
  178. package/dist/esm/resources/factories/ImageFactory.js.map +1 -1
  179. package/dist/esm/resources/factories/TextureFactory.d.ts +2 -2
  180. package/dist/esm/resources/factories/TextureFactory.js.map +1 -1
  181. package/dist/esm/resources/factories/VttFactory.d.ts +3 -3
  182. package/dist/esm/resources/factories/VttFactory.js +83 -6
  183. package/dist/esm/resources/factories/VttFactory.js.map +1 -1
  184. package/dist/exo.esm.js +4028 -1518
  185. package/dist/exo.esm.js.map +1 -1
  186. package/package.json +2 -1
  187. package/dist/esm/input/GamepadChannels.d.ts +0 -47
  188. package/dist/esm/input/GamepadChannels.js +0 -53
  189. package/dist/esm/input/GamepadChannels.js.map +0 -1
  190. package/dist/esm/input/GamepadControl.d.ts +0 -33
  191. package/dist/esm/input/GamepadControl.js +0 -42
  192. package/dist/esm/input/GamepadControl.js.map +0 -1
  193. package/dist/esm/input/Input.d.ts +0 -52
  194. package/dist/esm/input/Input.js +0 -90
  195. package/dist/esm/input/Input.js.map +0 -1
  196. package/dist/esm/particles/Particle.d.ts +0 -77
  197. package/dist/esm/particles/Particle.js +0 -143
  198. package/dist/esm/particles/Particle.js.map +0 -1
  199. package/dist/esm/particles/ParticleProperties.d.ts +0 -29
  200. package/dist/esm/particles/affectors/ColorAffector.d.ts +0 -30
  201. package/dist/esm/particles/affectors/ColorAffector.js +0 -55
  202. package/dist/esm/particles/affectors/ColorAffector.js.map +0 -1
  203. package/dist/esm/particles/affectors/ForceAffector.d.ts +0 -24
  204. package/dist/esm/particles/affectors/ForceAffector.js +0 -39
  205. package/dist/esm/particles/affectors/ForceAffector.js.map +0 -1
  206. package/dist/esm/particles/affectors/ParticleAffector.d.ts +0 -19
  207. package/dist/esm/particles/affectors/ScaleAffector.d.ts +0 -23
  208. package/dist/esm/particles/affectors/ScaleAffector.js +0 -38
  209. package/dist/esm/particles/affectors/ScaleAffector.js.map +0 -1
  210. package/dist/esm/particles/affectors/TorqueAffector.d.ts +0 -23
  211. package/dist/esm/particles/affectors/TorqueAffector.js +0 -37
  212. package/dist/esm/particles/affectors/TorqueAffector.js.map +0 -1
  213. package/dist/esm/particles/emitters/ParticleEmitter.d.ts +0 -19
  214. package/dist/esm/particles/emitters/ParticleOptions.d.ts +0 -62
  215. package/dist/esm/particles/emitters/ParticleOptions.js +0 -120
  216. package/dist/esm/particles/emitters/ParticleOptions.js.map +0 -1
  217. package/dist/esm/particles/emitters/UniversalEmitter.d.ts +0 -40
  218. package/dist/esm/particles/emitters/UniversalEmitter.js +0 -68
  219. package/dist/esm/particles/emitters/UniversalEmitter.js.map +0 -1
@@ -1,112 +1,209 @@
1
- import { Particle } from './Particle';
2
1
  import { Rectangle } from '@/math/Rectangle';
3
2
  import type { Time } from '@/core/Time';
4
3
  import { Drawable } from '@/rendering/Drawable';
5
- import type { Texture } from '@/rendering/texture/Texture';
6
- import type { ParticleEmitter } from '@/particles/emitters/ParticleEmitter';
7
- import type { ParticleAffector } from '@/particles/affectors/ParticleAffector';
4
+ import { Texture } from '@/rendering/texture/Texture';
5
+ import type { RenderBackend } from '@/rendering/RenderBackend';
6
+ import { Spritesheet } from '@/rendering/sprite/Spritesheet';
7
+ import type { SpawnModule } from './modules/SpawnModule';
8
+ import type { UpdateModule } from './modules/UpdateModule';
9
+ import type { DeathModule } from './modules/DeathModule';
10
+ import { ParticleGpuState } from './gpu/ParticleGpuState';
8
11
  /**
9
- * The central coordinator of the particle triad. `ParticleSystem` is a
10
- * {@link Drawable} that owns a list of {@link ParticleEmitter} spawners, a
11
- * list of {@link ParticleAffector} mutators, and the live/graveyard particle
12
- * pools. Each call to {@link ParticleSystem.update} runs all emitters to
13
- * spawn new particles, advances every live particle's position and lifetime,
14
- * retires expired ones to the graveyard for pooling, and runs all affectors
15
- * on the survivors.
12
+ * Options for {@link ParticleSystem}'s constructor orthogonal config
13
+ * that's independent of the texture source. Texture / frames / spritesheet
14
+ * live in positional arguments to enforce mutual exclusivity at the type
15
+ * level (you can't pass both a texture and a spritesheet by accident).
16
+ */
17
+ export interface ParticleSystemOptions {
18
+ /** Maximum particle count. Fixed at construction. Default 4096. */
19
+ capacity?: number;
20
+ /**
21
+ * Direct GPU device. Lets advanced consumers wire a `GPUDevice` owned
22
+ * outside an `Application` (or a mock device in tests). When omitted,
23
+ * the backend reference is captured automatically on the first
24
+ * {@link ParticleSystem.render} call — `WebGpuBackend` ⇒ GPU mode,
25
+ * anything else (incl. WebGL2) ⇒ CPU mode.
26
+ */
27
+ device?: GPUDevice;
28
+ }
29
+ /**
30
+ * The central coordinator of the particle pipeline. `ParticleSystem` is a
31
+ * {@link Drawable} that owns:
32
+ *
33
+ * - **SoA particle storage** — one typed array per attribute (position,
34
+ * velocity, scale, rotation, color, lifetime, ...), sized to a fixed
35
+ * capacity at construction. User code reads/writes via
36
+ * `system.posX[slot]`, `system.velX[slot]`, etc.
37
+ * - **Spawn modules** — write new particles into freshly allocated slots.
38
+ * - **Update modules** — mutate the live range each frame (forces, color
39
+ * blends, scale curves, drag, ...). Built-in modules ship both CPU and
40
+ * WGSL implementations; custom modules can opt into GPU acceleration by
41
+ * implementing `wgsl()`.
42
+ * - **Death modules** — fire once per dying particle, before its slot is
43
+ * recycled (sub-emitters, event hooks).
44
+ *
45
+ * **Auto-routing CPU vs GPU:** at first {@link update}, the system checks:
46
+ * if a `WebGpuBackend` was supplied AND every registered update module has
47
+ * `wgsl()`, the GPU path engages — a composite compute pipeline runs
48
+ * integration plus all module bodies in one dispatch and writes directly
49
+ * into the renderer's instance buffer (no CPU readback). Otherwise the CPU
50
+ * path runs the existing per-module `apply()` loops.
51
+ *
52
+ * **Per-frame order in {@link update} (CPU mode):**
53
+ * 1. Run every spawn module.
54
+ * 2. Integrate position from velocity, rotation from rotationSpeed, advance `elapsed`.
55
+ * 3. Run every update module on the live range.
56
+ * 4. Compact: scan `[0, liveCount)` forward, fire death modules on expired
57
+ * slots, copy survivors down. `liveCount` shrinks to the survivor count.
16
58
  *
17
- * Rendering reads {@link ParticleSystem.vertices} and
18
- * {@link ParticleSystem.texCoords} (lazily recomputed on texture-frame
19
- * changes) plus the live {@link ParticleSystem.particles} array to draw each
20
- * sprite.
59
+ * **Per-frame order in {@link update} (GPU mode):**
60
+ * 1. Run every spawn module (CPU writes initial values into the spawn slot).
61
+ * 2. Detect expiries on CPU (via `elapsed >= lifetime`); fire death modules;
62
+ * set `lifetime[slot] = -1` sentinel + clear `alive[slot]` so the GPU
63
+ * shader skips them. **No compaction** — slots are recycled on next spawn.
64
+ * 3. Dispatch the composite compute pipeline. Integration + update modules
65
+ * + pack-instances run in one pass; the instance buffer is written
66
+ * directly. CPU SoA stays as-is for spawn writes.
67
+ *
68
+ * **Coordinate space:** particle positions are LOCAL to the system. The
69
+ * system's `getGlobalTransform()` is applied on top during rendering — both
70
+ * the WebGL2 and WebGPU shaders multiply `projection * translation * rotated`.
71
+ * Setting world-space positions on individual particles double-translates.
72
+ * Position the system itself via `system.setPosition(...)` and emit relative
73
+ * to `(0, 0)`.
74
+ *
75
+ * @example
76
+ * // Backend-agnostic — runs CPU on WebGL2, GPU on WebGPU automatically.
77
+ * const system = new ParticleSystem(loader.get(Texture, 'spark'), {
78
+ * capacity: 8192,
79
+ * backend: app.backend,
80
+ * });
81
+ *
82
+ * system.addSpawnModule(new RateSpawn({ rate: new Constant(60), ... }));
83
+ * system.addUpdateModule(new ApplyForce(0, 980)); // gravity, GPU-eligible
84
+ * system.addUpdateModule(new ColorOverLifetime(fireGradient));
85
+ * scene.addChild(system);
21
86
  */
22
87
  export declare class ParticleSystem extends Drawable {
23
- private _emitters;
24
- private _affectors;
25
- private _particles;
26
- private _graveyard;
88
+ /** Maximum particle count this system will store. Fixed at construction. */
89
+ readonly capacity: number;
90
+ readonly posX: Float32Array;
91
+ readonly posY: Float32Array;
92
+ readonly velX: Float32Array;
93
+ readonly velY: Float32Array;
94
+ readonly scaleX: Float32Array;
95
+ readonly scaleY: Float32Array;
96
+ readonly rotations: Float32Array;
97
+ readonly rotationSpeeds: Float32Array;
98
+ readonly color: Uint32Array;
99
+ readonly elapsed: Float32Array;
100
+ readonly lifetime: Float32Array;
101
+ readonly textureIndex: Uint16Array;
102
+ /**
103
+ * Number of currently live particles. In CPU mode this is exact: slots
104
+ * `[0, liveCount)` are all alive after each `update()`. In GPU mode
105
+ * this is a high-water mark — slots `[0, liveCount)` may contain dead
106
+ * holes (filled in by future spawns); use {@link aliveCount} for the
107
+ * actual alive count.
108
+ */
109
+ liveCount: number;
110
+ /**
111
+ * Per-slot alive flag (1 = alive, 0 = dead). Maintained in both CPU
112
+ * and GPU mode. Custom modules iterating the live range should check
113
+ * this to skip dead slots in GPU mode.
114
+ */
115
+ readonly alive: Uint8Array;
116
+ private readonly _spawnModules;
117
+ private readonly _updateModules;
118
+ private readonly _deathModules;
119
+ private _backend;
120
+ private readonly _device;
121
+ private _gpuState;
122
+ private _gpuMode;
123
+ private _compiled;
124
+ private _spawnHint;
125
+ /**
126
+ * In GPU mode, slots whose CPU SoA values need re-uploading to the GPU
127
+ * (newly spawned, or just-expired with lifetime sentinel). Cleared
128
+ * after each compute dispatch. CPU never overwrites integrated GPU
129
+ * state — only dirty slots flow CPU → GPU.
130
+ */
131
+ private readonly _gpuDirtySlots;
27
132
  private _texture;
28
- private _textureFrame;
29
- private _vertices;
30
- private _texCoords;
133
+ private readonly _frames;
134
+ private readonly _textureFrame;
135
+ private readonly _vertices;
136
+ private readonly _texCoords;
31
137
  private _updateTexCoords;
32
138
  private _updateVertices;
33
- constructor(texture: Texture);
139
+ /** No texture — particles render as solid-color quads on a 1×1 white default. */
140
+ constructor(options?: ParticleSystemOptions);
141
+ /** Single texture, no atlas — every particle uses the full texture as one frame. */
142
+ constructor(texture: Texture, options?: ParticleSystemOptions);
143
+ /** Multi-frame atlas — each particle's `textureIndex` selects a frame. */
144
+ constructor(texture: Texture, frames: ReadonlyArray<Rectangle>, options?: ParticleSystemOptions);
145
+ /** Spritesheet shorthand — texture + frames pulled from the sheet. */
146
+ constructor(spritesheet: Spritesheet, options?: ParticleSystemOptions);
34
147
  get texture(): Texture;
35
148
  set texture(texture: Texture);
36
149
  get textureFrame(): Rectangle;
37
150
  set textureFrame(frame: Rectangle);
38
151
  /**
39
- * Quad corner offsets for the current {@link textureFrame}, in local
40
- * space as `[minX, minY, maxX, maxY]`. Recomputed lazily whenever
41
- * `textureFrame` changes. Used by the renderer to position each particle
42
- * sprite relative to its world position.
152
+ * Atlas frames declared on this system, or empty when the texture is
153
+ * used as a single frame. Each particle's `textureIndex[i]` selects
154
+ * an entry from this list; out-of-range indices are clamped to 0.
43
155
  */
156
+ get frames(): ReadonlyArray<Rectangle>;
157
+ /** `true` when the system declares more than one atlas frame. */
158
+ get hasAtlas(): boolean;
44
159
  get vertices(): Float32Array;
45
- /**
46
- * Packed UV coordinates for the current {@link textureFrame} as four
47
- * `Uint32` values, each encoding a `(u, v)` pair in the upper/lower 16
48
- * bits (normalised to 0–65535). Vertex order respects
49
- * {@link Texture.flipY}. Recomputed lazily on texture or frame changes.
50
- */
51
160
  get texCoords(): Uint32Array;
52
- get emitters(): Array<ParticleEmitter>;
53
- get affectors(): Array<ParticleAffector>;
54
- get particles(): Array<Particle>;
55
- /**
56
- * Pool of expired {@link Particle} instances waiting to be recycled.
57
- * {@link requestParticle} pops from this array before allocating a new
58
- * instance, keeping GC pressure low during sustained emission.
59
- */
60
- get graveyard(): Array<Particle>;
61
- /**
62
- * Replaces the particle sprite texture and resets the texture frame to
63
- * cover the full new texture. No-ops if `texture` is the same instance.
64
- */
161
+ /** `true` when the system is running on the GPU compute pipeline. */
162
+ get gpuMode(): boolean;
163
+ /** GPU-side state, or `null` in CPU mode. */
164
+ get gpuState(): ParticleGpuState | null;
165
+ /** Actual count of live particles (slots with `alive[i] === 1`). May differ from `liveCount` in GPU mode. */
166
+ get aliveCount(): number;
167
+ get spawnModules(): ReadonlyArray<SpawnModule>;
168
+ get updateModules(): ReadonlyArray<UpdateModule>;
169
+ get deathModules(): ReadonlyArray<DeathModule>;
65
170
  setTexture(texture: Texture): this;
66
- /**
67
- * Sets the sub-rectangle of the texture used as the particle sprite,
68
- * invalidating cached vertices and UV coordinates and updating the system's
69
- * local bounds to match the frame dimensions.
70
- */
71
171
  setTextureFrame(frame: Rectangle): this;
72
- /** Resets the texture frame to the full dimensions of the current texture. */
73
172
  resetTextureFrame(): this;
74
- /** Registers `emitter` to be called each tick during {@link update}. */
75
- addEmitter(emitter: ParticleEmitter): this;
76
- /** Destroys and removes all registered emitters. */
77
- clearEmitters(): this;
78
- /** Registers `affector` to run on every live particle each tick during {@link update}. */
79
- addAffector(affector: ParticleAffector): this;
80
- /** Destroys and removes all registered affectors. */
81
- clearAffectors(): this;
82
- /**
83
- * Returns a recycled particle from the {@link graveyard}, or allocates a
84
- * new one if the pool is empty. Call {@link Particle.applyOptions}
85
- * immediately after to reset its state before passing it to
86
- * {@link emitParticle}.
87
- */
88
- requestParticle(): Particle;
89
- /** Adds a fully-configured `particle` to the live pool. Typically called by emitters. */
90
- emitParticle(particle: Particle): this;
91
- /**
92
- * Advances a single particle by one `delta` step: increments
93
- * `elapsedLifetime`, integrates velocity into position, and applies
94
- * `rotationSpeed` to rotation. Called for every live particle by
95
- * {@link update} before the affector pass.
96
- */
97
- updateParticle(particle: Particle, delta: Time): this;
173
+ addSpawnModule(mod: SpawnModule): this;
174
+ addUpdateModule(mod: UpdateModule): this;
175
+ addDeathModule(mod: DeathModule): this;
176
+ clearSpawnModules(): this;
177
+ clearUpdateModules(): this;
178
+ clearDeathModules(): this;
98
179
  /**
99
- * Destroys and removes all particles from both the live pool and the
100
- * graveyard. Use when resetting or recycling the entire system.
180
+ * Allocates a particle slot and returns its index. Returns `-1` when
181
+ * the system is at {@link capacity}.
182
+ *
183
+ * **CPU mode:** slots are dense in `[0, liveCount)`. `spawn()` returns
184
+ * the next sequential slot; `liveCount++`.
185
+ *
186
+ * **GPU mode:** slots may have dead holes. `spawn()` finds the first
187
+ * `alive[i] === 0` slot via a round-robin hint pointer (amortised O(1),
188
+ * worst case O(capacity) on full systems).
101
189
  */
190
+ spawn(): number;
191
+ /** Resets the system to zero live particles without destroying it. */
102
192
  clearParticles(): this;
103
193
  /**
104
- * Advances the full simulation by one `delta` step: runs all emitters,
105
- * then for each live particle calls {@link updateParticle}, moves expired
106
- * ones to the {@link graveyard}, and runs all affectors on survivors.
107
- * The particle array is iterated in reverse to allow in-place splice
108
- * without re-indexing.
194
+ * Engine-side render hook. Captures the active backend on each call so
195
+ * the next `update()` can compile a GPU pipeline if the backend turned
196
+ * out to be `WebGpuBackend`. Re-captures and rebuilds when the backend
197
+ * reference changes (e.g. after device-loss recovery).
109
198
  */
199
+ render(backend: RenderBackend): this;
200
+ /** Per-frame entry point. Routes to CPU or GPU pipeline based on auto-detection at first call. */
110
201
  update(delta: Time): this;
111
202
  destroy(): void;
203
+ private _compile;
204
+ private _spawnCpu;
205
+ private _spawnGpu;
206
+ private _updateCpu;
207
+ private _updateGpu;
208
+ private _copySlot;
112
209
  }