@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
@@ -0,0 +1,59 @@
1
+ import { UpdateModule } from './UpdateModule.js';
2
+
3
+ /// <reference types="@webgpu/types" />
4
+ const lookupSize = 256;
5
+ /**
6
+ * Sets every live particle's scale to a curve sampled at the particle's
7
+ * current lifetime ratio. Both axes share one curve — for non-uniform
8
+ * scaling layer two ScaleOverLifetime modules with separate `axis` filters
9
+ * (or extend with a per-axis variant).
10
+ *
11
+ * Common patterns: shrink-to-zero (start at 1, end at 0), pulse (sine-like
12
+ * curve up to peak then down), slow-grow (linear ramp).
13
+ *
14
+ * GPU-eligible: the curve is uploaded once as a 256-tap 1D R32F texture and
15
+ * sampled with linear filtering on the GPU — no curve evaluation cost in
16
+ * the inner loop.
17
+ */
18
+ class ScaleOverLifetime extends UpdateModule {
19
+ curve;
20
+ constructor(curve) {
21
+ super();
22
+ this.curve = curve;
23
+ }
24
+ apply(system, _dt) {
25
+ const { scaleX, scaleY, elapsed, lifetime, liveCount } = system;
26
+ const curve = this.curve;
27
+ for (let i = 0; i < liveCount; i++) {
28
+ const t = elapsed[i] / lifetime[i];
29
+ const s = curve.evaluate(t);
30
+ scaleX[i] = s;
31
+ scaleY[i] = s;
32
+ }
33
+ }
34
+ wgsl() {
35
+ return {
36
+ key: 'ScaleOverLifetime',
37
+ textures: [{ name: 'curve', format: 'r32float' }],
38
+ body: `
39
+ let scaleT = clamp(timing[idx].x / max(timing[idx].y, 0.000001), 0.0, 1.0);
40
+ let scaleSample = textureSampleLevel(u_ScaleOverLifetime_curve, u_ScaleOverLifetime_curve_sampler, scaleT, 0.0).r;
41
+ scales[idx] = vec2<f32>(scaleSample, scaleSample);
42
+ `,
43
+ };
44
+ }
45
+ uploadTextures(device, textures) {
46
+ const texture = textures.get('curve');
47
+ if (texture === undefined) {
48
+ return;
49
+ }
50
+ const data = new Float32Array(lookupSize);
51
+ for (let i = 0; i < lookupSize; i++) {
52
+ data[i] = this.curve.evaluate(i / (lookupSize - 1));
53
+ }
54
+ device.queue.writeTexture({ texture }, data.buffer, { offset: 0, bytesPerRow: lookupSize * 4, rowsPerImage: 1 }, { width: lookupSize, height: 1, depthOrArrayLayers: 1 });
55
+ }
56
+ }
57
+
58
+ export { ScaleOverLifetime };
59
+ //# sourceMappingURL=ScaleOverLifetime.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ScaleOverLifetime.js","sources":["../../../../../src/particles/modules/ScaleOverLifetime.ts"],"sourcesContent":[null],"names":[],"mappings":";;AAAA;AAOA,MAAM,UAAU,GAAG,GAAG;AAEtB;;;;;;;;;;;;AAYG;AACG,MAAO,iBAAkB,SAAQ,YAAY,CAAA;AACxC,IAAA,KAAK;AAEZ,IAAA,WAAA,CAAmB,KAAY,EAAA;AAC3B,QAAA,KAAK,EAAE;AACP,QAAA,IAAI,CAAC,KAAK,GAAG,KAAK;IACtB;IAEgB,KAAK,CAAC,MAAsB,EAAE,GAAW,EAAA;AACrD,QAAA,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,QAAQ,EAAE,SAAS,EAAE,GAAG,MAAM;AAC/D,QAAA,MAAM,KAAK,GAAG,IAAI,CAAC,KAAK;AAExB,QAAA,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,SAAS,EAAE,CAAC,EAAE,EAAE;YAChC,MAAM,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC,GAAG,QAAQ,CAAC,CAAC,CAAC;YAClC,MAAM,CAAC,GAAG,KAAK,CAAC,QAAQ,CAAC,CAAC,CAAC;AAE3B,YAAA,MAAM,CAAC,CAAC,CAAC,GAAG,CAAC;AACb,YAAA,MAAM,CAAC,CAAC,CAAC,GAAG,CAAC;QACjB;IACJ;IAEgB,IAAI,GAAA;QAChB,OAAO;AACH,YAAA,GAAG,EAAE,mBAAmB;YACxB,QAAQ,EAAE,CAAC,EAAE,IAAI,EAAE,OAAO,EAAE,MAAM,EAAE,UAAU,EAAE,CAAC;AACjD,YAAA,IAAI,EAAE;;;;AAIL,YAAA,CAAA;SACJ;IACL;IAEgB,cAAc,CAAC,MAAiB,EAAE,QAAyC,EAAA;QACvF,MAAM,OAAO,GAAG,QAAQ,CAAC,GAAG,CAAC,OAAO,CAAC;AAErC,QAAA,IAAI,OAAO,KAAK,SAAS,EAAE;YACvB;QACJ;AAEA,QAAA,MAAM,IAAI,GAAG,IAAI,YAAY,CAAC,UAAU,CAAC;AAEzC,QAAA,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,UAAU,EAAE,CAAC,EAAE,EAAE;AACjC,YAAA,IAAI,CAAC,CAAC,CAAC,GAAG,IAAI,CAAC,KAAK,CAAC,QAAQ,CAAC,CAAC,IAAI,UAAU,GAAG,CAAC,CAAC,CAAC;QACvD;AAEA,QAAA,MAAM,CAAC,KAAK,CAAC,YAAY,CACrB,EAAE,OAAO,EAAE,EACX,IAAI,CAAC,MAAqB,EAC1B,EAAE,MAAM,EAAE,CAAC,EAAE,WAAW,EAAE,UAAU,GAAG,CAAC,EAAE,YAAY,EAAE,CAAC,EAAE,EAC3D,EAAE,KAAK,EAAE,UAAU,EAAE,MAAM,EAAE,CAAC,EAAE,kBAAkB,EAAE,CAAC,EAAE,CAC1D;IACL;AACH;;;;"}
@@ -0,0 +1,30 @@
1
+ import type { ParticleSystem } from '@/particles/ParticleSystem';
2
+ /**
3
+ * Per-frame particle spawner. Subclasses decide how many particles to emit
4
+ * each tick (rate-based, burst, on-demand) and write their initial values
5
+ * directly into the system's typed-array slots.
6
+ *
7
+ * Implementation pattern:
8
+ *
9
+ * ```ts
10
+ * apply(system, dt) {
11
+ * const count = this.computeSpawnCount(dt);
12
+ * for (let i = 0; i < count; i++) {
13
+ * const slot = system.spawn();
14
+ * if (slot < 0) break; // capacity exhausted
15
+ * system.posX[slot] = ...;
16
+ * system.velX[slot] = ...;
17
+ * system.lifetime[slot] = ...;
18
+ * // ...etc
19
+ * }
20
+ * }
21
+ * ```
22
+ *
23
+ * Spawn modules run before integration each frame. Multiple modules can be
24
+ * registered on one system and execute in registration order.
25
+ */
26
+ export declare abstract class SpawnModule {
27
+ abstract apply(system: ParticleSystem, dt: number): void;
28
+ /** Optional cleanup hook called from `ParticleSystem.destroy`. */
29
+ destroy(): void;
30
+ }
@@ -0,0 +1,31 @@
1
+ /**
2
+ * Per-frame particle spawner. Subclasses decide how many particles to emit
3
+ * each tick (rate-based, burst, on-demand) and write their initial values
4
+ * directly into the system's typed-array slots.
5
+ *
6
+ * Implementation pattern:
7
+ *
8
+ * ```ts
9
+ * apply(system, dt) {
10
+ * const count = this.computeSpawnCount(dt);
11
+ * for (let i = 0; i < count; i++) {
12
+ * const slot = system.spawn();
13
+ * if (slot < 0) break; // capacity exhausted
14
+ * system.posX[slot] = ...;
15
+ * system.velX[slot] = ...;
16
+ * system.lifetime[slot] = ...;
17
+ * // ...etc
18
+ * }
19
+ * }
20
+ * ```
21
+ *
22
+ * Spawn modules run before integration each frame. Multiple modules can be
23
+ * registered on one system and execute in registration order.
24
+ */
25
+ class SpawnModule {
26
+ /** Optional cleanup hook called from `ParticleSystem.destroy`. */
27
+ destroy() { }
28
+ }
29
+
30
+ export { SpawnModule };
31
+ //# sourceMappingURL=SpawnModule.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"SpawnModule.js","sources":["../../../../../src/particles/modules/SpawnModule.ts"],"sourcesContent":[null],"names":[],"mappings":"AAEA;;;;;;;;;;;;;;;;;;;;;;;AAuBG;MACmB,WAAW,CAAA;;AAGtB,IAAA,OAAO,KAAU;AAC3B;;;;"}
@@ -0,0 +1,24 @@
1
+ import { DeathModule } from './DeathModule';
2
+ import type { ParticleSystem } from '@/particles/ParticleSystem';
3
+ import type { SpawnModule } from './SpawnModule';
4
+ /**
5
+ * Sub-emitter: triggers a child {@link SpawnModule} on a target system at
6
+ * the dying particle's position. Use for explosion-on-impact, sparks at
7
+ * end-of-life, multi-stage VFX.
8
+ *
9
+ * The child module receives a synthesized `dt` of 0 — it must spawn
10
+ * immediately rather than rely on rate accumulation. {@link BurstSpawn}
11
+ * works naturally; {@link RateSpawn} is the wrong fit here.
12
+ *
13
+ * Position is the only field forwarded — child distributions decide
14
+ * everything else. To keep child particles riding the parent's velocity,
15
+ * configure the child's velocity distribution to match.
16
+ */
17
+ export declare class SpawnOnDeath extends DeathModule {
18
+ targetSystem: ParticleSystem;
19
+ spawner: SpawnModule;
20
+ /** Number of times to invoke the spawner per dying particle. Default 1. */
21
+ count: number;
22
+ constructor(targetSystem: ParticleSystem, spawner: SpawnModule, count?: number);
23
+ onDeath(parent: ParticleSystem, slot: number): void;
24
+ }
@@ -0,0 +1,47 @@
1
+ import { DeathModule } from './DeathModule.js';
2
+
3
+ /**
4
+ * Sub-emitter: triggers a child {@link SpawnModule} on a target system at
5
+ * the dying particle's position. Use for explosion-on-impact, sparks at
6
+ * end-of-life, multi-stage VFX.
7
+ *
8
+ * The child module receives a synthesized `dt` of 0 — it must spawn
9
+ * immediately rather than rely on rate accumulation. {@link BurstSpawn}
10
+ * works naturally; {@link RateSpawn} is the wrong fit here.
11
+ *
12
+ * Position is the only field forwarded — child distributions decide
13
+ * everything else. To keep child particles riding the parent's velocity,
14
+ * configure the child's velocity distribution to match.
15
+ */
16
+ class SpawnOnDeath extends DeathModule {
17
+ targetSystem;
18
+ spawner;
19
+ /** Number of times to invoke the spawner per dying particle. Default 1. */
20
+ count;
21
+ constructor(targetSystem, spawner, count = 1) {
22
+ super();
23
+ this.targetSystem = targetSystem;
24
+ this.spawner = spawner;
25
+ this.count = count;
26
+ }
27
+ onDeath(parent, slot) {
28
+ const target = this.targetSystem;
29
+ const x = parent.posX[slot];
30
+ const y = parent.posY[slot];
31
+ // Snapshot the target's pre-spawn count so we can apply the
32
+ // position to whichever slots the spawner adds.
33
+ const before = target.liveCount;
34
+ for (let n = 0; n < this.count; n++) {
35
+ this.spawner.apply(target, 0);
36
+ }
37
+ const added = target.liveCount - before;
38
+ for (let i = 0; i < added; i++) {
39
+ const dst = before + i;
40
+ target.posX[dst] += x;
41
+ target.posY[dst] += y;
42
+ }
43
+ }
44
+ }
45
+
46
+ export { SpawnOnDeath };
47
+ //# sourceMappingURL=SpawnOnDeath.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"SpawnOnDeath.js","sources":["../../../../../src/particles/modules/SpawnOnDeath.ts"],"sourcesContent":[null],"names":[],"mappings":";;AAIA;;;;;;;;;;;;AAYG;AACG,MAAO,YAAa,SAAQ,WAAW,CAAA;AAClC,IAAA,YAAY;AACZ,IAAA,OAAO;;AAGP,IAAA,KAAK;AAEZ,IAAA,WAAA,CAAmB,YAA4B,EAAE,OAAoB,EAAE,QAAgB,CAAC,EAAA;AACpF,QAAA,KAAK,EAAE;AACP,QAAA,IAAI,CAAC,YAAY,GAAG,YAAY;AAChC,QAAA,IAAI,CAAC,OAAO,GAAG,OAAO;AACtB,QAAA,IAAI,CAAC,KAAK,GAAG,KAAK;IACtB;IAEgB,OAAO,CAAC,MAAsB,EAAE,IAAY,EAAA;AACxD,QAAA,MAAM,MAAM,GAAG,IAAI,CAAC,YAAY;QAChC,MAAM,CAAC,GAAG,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC;QAC3B,MAAM,CAAC,GAAG,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC;;;AAI3B,QAAA,MAAM,MAAM,GAAG,MAAM,CAAC,SAAS;AAE/B,QAAA,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,IAAI,CAAC,KAAK,EAAE,CAAC,EAAE,EAAE;YACjC,IAAI,CAAC,OAAO,CAAC,KAAK,CAAC,MAAM,EAAE,CAAC,CAAC;QACjC;AAEA,QAAA,MAAM,KAAK,GAAG,MAAM,CAAC,SAAS,GAAG,MAAM;AAEvC,QAAA,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,KAAK,EAAE,CAAC,EAAE,EAAE;AAC5B,YAAA,MAAM,GAAG,GAAG,MAAM,GAAG,CAAC;AAEtB,YAAA,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC;AACrB,YAAA,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC;QACzB;IACJ;AACH;;;;"}
@@ -0,0 +1,30 @@
1
+ import { UpdateModule } from './UpdateModule';
2
+ import type { ParticleSystem } from '@/particles/ParticleSystem';
3
+ import type { WgslContribution } from './WgslContribution';
4
+ /**
5
+ * Adds a smooth pseudo-random force field that animates over time.
6
+ * Implemented as 2D value noise with cubic Hermite smoothing — sampled
7
+ * twice per particle (offset to decorrelate x and y components) and scaled
8
+ * by `strength`. The field evolves at `timeScale` units per second; lower
9
+ * values produce slow-moving currents, higher values produce buzzy chaos.
10
+ *
11
+ * `frequency` controls the spatial granularity: small values (≈ 0.005)
12
+ * yield broad swirls across the playfield, large values (≈ 0.1) produce
13
+ * tight per-particle jitter.
14
+ *
15
+ * Use cases: smoke turbulence, organic swirls, wind eddies, dust haze.
16
+ * Pair with {@link Drag} to keep particle velocities bounded.
17
+ *
18
+ * GPU-eligible. The noise function is identical on CPU and GPU so visual
19
+ * results match across backends (modulo float precision).
20
+ */
21
+ export declare class Turbulence extends UpdateModule {
22
+ strength: number;
23
+ frequency: number;
24
+ timeScale: number;
25
+ private _time;
26
+ constructor(strength: number, frequency?: number, timeScale?: number);
27
+ apply(system: ParticleSystem, dt: number): void;
28
+ wgsl(): WgslContribution;
29
+ writeUniforms(view: DataView, offset: number, dt: number): void;
30
+ }
@@ -0,0 +1,122 @@
1
+ import { UpdateModule } from './UpdateModule.js';
2
+
3
+ /**
4
+ * Adds a smooth pseudo-random force field that animates over time.
5
+ * Implemented as 2D value noise with cubic Hermite smoothing — sampled
6
+ * twice per particle (offset to decorrelate x and y components) and scaled
7
+ * by `strength`. The field evolves at `timeScale` units per second; lower
8
+ * values produce slow-moving currents, higher values produce buzzy chaos.
9
+ *
10
+ * `frequency` controls the spatial granularity: small values (≈ 0.005)
11
+ * yield broad swirls across the playfield, large values (≈ 0.1) produce
12
+ * tight per-particle jitter.
13
+ *
14
+ * Use cases: smoke turbulence, organic swirls, wind eddies, dust haze.
15
+ * Pair with {@link Drag} to keep particle velocities bounded.
16
+ *
17
+ * GPU-eligible. The noise function is identical on CPU and GPU so visual
18
+ * results match across backends (modulo float precision).
19
+ */
20
+ class Turbulence extends UpdateModule {
21
+ strength;
22
+ frequency;
23
+ timeScale;
24
+ _time = 0;
25
+ constructor(strength, frequency = 0.01, timeScale = 1) {
26
+ super();
27
+ this.strength = strength;
28
+ this.frequency = frequency;
29
+ this.timeScale = timeScale;
30
+ }
31
+ apply(system, dt) {
32
+ this._time += dt * this.timeScale;
33
+ const t = this._time;
34
+ const f = this.frequency;
35
+ const s = this.strength * dt;
36
+ const { posX, posY, velX, velY, liveCount } = system;
37
+ for (let i = 0; i < liveCount; i++) {
38
+ const x = posX[i] * f;
39
+ const y = posY[i] * f;
40
+ const nx = valueNoise2(x + t, y);
41
+ const ny = valueNoise2(x, y + t + 17.31);
42
+ velX[i] += (nx * 2 - 1) * s;
43
+ velY[i] += (ny * 2 - 1) * s;
44
+ }
45
+ }
46
+ wgsl() {
47
+ return {
48
+ key: 'Turbulence',
49
+ uniforms: [
50
+ { name: 'strength', type: 'f32' },
51
+ { name: 'frequency', type: 'f32' },
52
+ { name: 'time', type: 'f32' },
53
+ { name: '_pad0', type: 'f32' },
54
+ ],
55
+ prelude: `
56
+ fn exojs_turbulence_hash21(p: vec2<f32>) -> f32 {
57
+ let n = sin(dot(p, vec2<f32>(127.1, 311.7))) * 43758.5453;
58
+ return fract(n);
59
+ }
60
+
61
+ fn exojs_turbulence_valueNoise2(x: f32, y: f32) -> f32 {
62
+ let xi = floor(x);
63
+ let yi = floor(y);
64
+ let xf = x - xi;
65
+ let yf = y - yi;
66
+ let u = xf * xf * (3.0 - 2.0 * xf);
67
+ let v = yf * yf * (3.0 - 2.0 * yf);
68
+ let a = exojs_turbulence_hash21(vec2<f32>(xi, yi));
69
+ let b = exojs_turbulence_hash21(vec2<f32>(xi + 1.0, yi));
70
+ let c = exojs_turbulence_hash21(vec2<f32>(xi, yi + 1.0));
71
+ let d = exojs_turbulence_hash21(vec2<f32>(xi + 1.0, yi + 1.0));
72
+ let ab = a + (b - a) * u;
73
+ let cd = c + (d - c) * u;
74
+ return ab + (cd - ab) * v;
75
+ }
76
+ `,
77
+ body: `
78
+ let turbF = modules.u_Turbulence.frequency;
79
+ let turbT = modules.u_Turbulence.time;
80
+ let turbS = modules.u_Turbulence.strength * dt;
81
+ let turbX = positions[idx].x * turbF;
82
+ let turbY = positions[idx].y * turbF;
83
+ let turbNx = exojs_turbulence_valueNoise2(turbX + turbT, turbY);
84
+ let turbNy = exojs_turbulence_valueNoise2(turbX, turbY + turbT + 17.31);
85
+ velocities[idx] = velocities[idx] + vec2<f32>(turbNx * 2.0 - 1.0, turbNy * 2.0 - 1.0) * turbS;
86
+ `,
87
+ };
88
+ }
89
+ writeUniforms(view, offset, dt) {
90
+ // GPU mode: apply() never runs, so advance _time here once per frame.
91
+ // CPU mode: apply() already advances _time before update finishes,
92
+ // and writeUniforms is not called → no double-advance.
93
+ this._time += dt * this.timeScale;
94
+ view.setFloat32(offset + 0, this.strength, true);
95
+ view.setFloat32(offset + 4, this.frequency, true);
96
+ view.setFloat32(offset + 8, this._time, true);
97
+ view.setFloat32(offset + 12, 0, true);
98
+ }
99
+ }
100
+ function hash21(x, y) {
101
+ let n = Math.sin(x * 127.1 + y * 311.7) * 43758.5453;
102
+ n = n - Math.floor(n);
103
+ return n;
104
+ }
105
+ function valueNoise2(x, y) {
106
+ const xi = Math.floor(x);
107
+ const yi = Math.floor(y);
108
+ const xf = x - xi;
109
+ const yf = y - yi;
110
+ const u = xf * xf * (3 - 2 * xf);
111
+ const v = yf * yf * (3 - 2 * yf);
112
+ const a = hash21(xi, yi);
113
+ const b = hash21(xi + 1, yi);
114
+ const c = hash21(xi, yi + 1);
115
+ const d = hash21(xi + 1, yi + 1);
116
+ const ab = a + (b - a) * u;
117
+ const cd = c + (d - c) * u;
118
+ return ab + (cd - ab) * v;
119
+ }
120
+
121
+ export { Turbulence };
122
+ //# sourceMappingURL=Turbulence.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Turbulence.js","sources":["../../../../../src/particles/modules/Turbulence.ts"],"sourcesContent":[null],"names":[],"mappings":"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"}
@@ -0,0 +1,95 @@
1
+ import type { ParticleSystem } from '@/particles/ParticleSystem';
2
+ import type { WgslContribution } from './WgslContribution';
3
+ /**
4
+ * Per-frame, per-batch mutator. Operates on the system's SoA storage
5
+ * directly — typically a single tight loop over `[0, system.liveCount)`
6
+ * that reads/writes the relevant `Float32Array`s.
7
+ *
8
+ * Implementations must always provide a CPU `apply()`. To make a module
9
+ * GPU-eligible (executed inside the system's composite compute shader on
10
+ * WebGPU backends), additionally implement {@link wgsl} and
11
+ * {@link writeUniforms}. Modules that declare a {@link WgslContribution}
12
+ * may also opt to declare a 1D texture binding via the `textures` field
13
+ * (used by `Curve` / `Gradient`-driven modules) — in which case
14
+ * {@link uploadTextures} runs once at compile time to upload the data.
15
+ *
16
+ * Implementation pattern (CPU-only module):
17
+ *
18
+ * ```ts
19
+ * class MyModule extends UpdateModule {
20
+ * apply(system, dt) {
21
+ * const { velX, velY, liveCount } = system;
22
+ * for (let i = 0; i < liveCount; i++) { velX[i] *= 0.99; velY[i] *= 0.99; }
23
+ * }
24
+ * }
25
+ * ```
26
+ *
27
+ * Implementation pattern (GPU-eligible module):
28
+ *
29
+ * ```ts
30
+ * class MyForce extends UpdateModule {
31
+ * constructor(public ax: number, public ay: number) { super(); }
32
+ *
33
+ * apply(system, dt) {
34
+ * const { velX, velY, liveCount } = system;
35
+ * for (let i = 0; i < liveCount; i++) { velX[i] += this.ax * dt; velY[i] += this.ay * dt; }
36
+ * }
37
+ *
38
+ * wgsl(): WgslContribution {
39
+ * return {
40
+ * key: 'MyForce',
41
+ * uniforms: [{ name: 'ax', type: 'f32' }, { name: 'ay', type: 'f32' }],
42
+ * body: `velX[idx] += u_MyForce.ax * dt; velY[idx] += u_MyForce.ay * dt;`,
43
+ * };
44
+ * }
45
+ *
46
+ * writeUniforms(view, offset) {
47
+ * view.setFloat32(offset + 0, this.ax, true);
48
+ * view.setFloat32(offset + 4, this.ay, true);
49
+ * }
50
+ * }
51
+ * ```
52
+ *
53
+ * If *any* registered update module on a system lacks `wgsl()`, the system
54
+ * forces CPU mode regardless of backend — preserving the contract that
55
+ * `apply()` is always honoured. Built-in modules ship both
56
+ * implementations; custom modules can opt into GPU acceleration at their
57
+ * authors' discretion.
58
+ *
59
+ * Update modules run after integration each frame. Multiple modules execute
60
+ * in registration order; later modules see the effects of earlier ones.
61
+ */
62
+ export declare abstract class UpdateModule {
63
+ abstract apply(system: ParticleSystem, dt: number): void;
64
+ /**
65
+ * Override to declare a GPU contribution. Returning a {@link WgslContribution}
66
+ * makes this module GPU-eligible; omitting (or returning undefined)
67
+ * forces CPU mode for any system that uses this module.
68
+ */
69
+ wgsl?(): WgslContribution;
70
+ /**
71
+ * Write this module's current uniform values into the shared uniform
72
+ * buffer at `byteOffset`. Layout must match the field declarations
73
+ * returned by {@link wgsl} (in the same order).
74
+ *
75
+ * Receives the current frame `dt` (seconds). Modules tracking
76
+ * accumulated time (e.g. noise/turbulence) should advance their
77
+ * internal counter here to stay in sync when running in GPU mode
78
+ * (where {@link apply} is not called).
79
+ *
80
+ * Required when {@link wgsl} declares uniforms. Called every frame by
81
+ * the system before dispatching compute.
82
+ */
83
+ writeUniforms?(view: DataView, byteOffset: number, dt: number): void;
84
+ /**
85
+ * Upload texture data (Curve/Gradient lookup tables) to the GPU at
86
+ * compile time. Receives the GPUDevice and a map of texture bindings
87
+ * keyed by the `name` in {@link WgslContribution.textures}. Called once
88
+ * after pipeline creation.
89
+ *
90
+ * Required when {@link wgsl} declares textures.
91
+ */
92
+ uploadTextures?(device: GPUDevice, textures: ReadonlyMap<string, GPUTexture>): void;
93
+ /** Optional cleanup hook called from `ParticleSystem.destroy`. */
94
+ destroy(): void;
95
+ }
@@ -0,0 +1,66 @@
1
+ /**
2
+ * Per-frame, per-batch mutator. Operates on the system's SoA storage
3
+ * directly — typically a single tight loop over `[0, system.liveCount)`
4
+ * that reads/writes the relevant `Float32Array`s.
5
+ *
6
+ * Implementations must always provide a CPU `apply()`. To make a module
7
+ * GPU-eligible (executed inside the system's composite compute shader on
8
+ * WebGPU backends), additionally implement {@link wgsl} and
9
+ * {@link writeUniforms}. Modules that declare a {@link WgslContribution}
10
+ * may also opt to declare a 1D texture binding via the `textures` field
11
+ * (used by `Curve` / `Gradient`-driven modules) — in which case
12
+ * {@link uploadTextures} runs once at compile time to upload the data.
13
+ *
14
+ * Implementation pattern (CPU-only module):
15
+ *
16
+ * ```ts
17
+ * class MyModule extends UpdateModule {
18
+ * apply(system, dt) {
19
+ * const { velX, velY, liveCount } = system;
20
+ * for (let i = 0; i < liveCount; i++) { velX[i] *= 0.99; velY[i] *= 0.99; }
21
+ * }
22
+ * }
23
+ * ```
24
+ *
25
+ * Implementation pattern (GPU-eligible module):
26
+ *
27
+ * ```ts
28
+ * class MyForce extends UpdateModule {
29
+ * constructor(public ax: number, public ay: number) { super(); }
30
+ *
31
+ * apply(system, dt) {
32
+ * const { velX, velY, liveCount } = system;
33
+ * for (let i = 0; i < liveCount; i++) { velX[i] += this.ax * dt; velY[i] += this.ay * dt; }
34
+ * }
35
+ *
36
+ * wgsl(): WgslContribution {
37
+ * return {
38
+ * key: 'MyForce',
39
+ * uniforms: [{ name: 'ax', type: 'f32' }, { name: 'ay', type: 'f32' }],
40
+ * body: `velX[idx] += u_MyForce.ax * dt; velY[idx] += u_MyForce.ay * dt;`,
41
+ * };
42
+ * }
43
+ *
44
+ * writeUniforms(view, offset) {
45
+ * view.setFloat32(offset + 0, this.ax, true);
46
+ * view.setFloat32(offset + 4, this.ay, true);
47
+ * }
48
+ * }
49
+ * ```
50
+ *
51
+ * If *any* registered update module on a system lacks `wgsl()`, the system
52
+ * forces CPU mode regardless of backend — preserving the contract that
53
+ * `apply()` is always honoured. Built-in modules ship both
54
+ * implementations; custom modules can opt into GPU acceleration at their
55
+ * authors' discretion.
56
+ *
57
+ * Update modules run after integration each frame. Multiple modules execute
58
+ * in registration order; later modules see the effects of earlier ones.
59
+ */
60
+ class UpdateModule {
61
+ /** Optional cleanup hook called from `ParticleSystem.destroy`. */
62
+ destroy() { }
63
+ }
64
+
65
+ export { UpdateModule };
66
+ //# sourceMappingURL=UpdateModule.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"UpdateModule.js","sources":["../../../../../src/particles/modules/UpdateModule.ts"],"sourcesContent":[null],"names":[],"mappings":"AAGA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA0DG;MACmB,YAAY,CAAA;;AAoCvB,IAAA,OAAO,KAAU;AAC3B;;;;"}
@@ -0,0 +1,30 @@
1
+ import { UpdateModule } from './UpdateModule';
2
+ import type { ParticleSystem } from '@/particles/ParticleSystem';
3
+ import type { Curve } from '@/particles/distributions/Curve';
4
+ import type { WgslContribution } from './WgslContribution';
5
+ /**
6
+ * Multiplies every live particle's velocity by a curve sampled at the
7
+ * particle's current `elapsed / lifetime` ratio. The same scalar is applied
8
+ * to both axes — the direction is preserved, only the magnitude scales.
9
+ *
10
+ * Common patterns:
11
+ * - **Snappy spawn, slow drift:** `[0,1]→[1,0.1]` — fast initial motion
12
+ * that decays over lifetime.
13
+ * - **Late acceleration:** `[0,0.2]→[1,1]` — particles ease in.
14
+ * - **Pulse:** sine-shaped curve for breathing-style motion.
15
+ *
16
+ * Note: the curve **replaces** velocity each frame relative to the previous
17
+ * frame's velocity, so the effect is multiplicative. Pair with `ApplyForce`
18
+ * if you want a constant external acceleration on top.
19
+ *
20
+ * GPU-eligible: uploads the curve as a 256-tap 1D R32F texture; sampled
21
+ * once per particle per frame.
22
+ */
23
+ export declare class VelocityOverLifetime extends UpdateModule {
24
+ curve: Curve;
25
+ private _prevSample;
26
+ constructor(curve: Curve);
27
+ apply(system: ParticleSystem, _dt: number): void;
28
+ wgsl(): WgslContribution;
29
+ uploadTextures(device: GPUDevice, textures: ReadonlyMap<string, GPUTexture>): void;
30
+ }
@@ -0,0 +1,84 @@
1
+ import { UpdateModule } from './UpdateModule.js';
2
+
3
+ /// <reference types="@webgpu/types" />
4
+ const lookupSize = 256;
5
+ /**
6
+ * Multiplies every live particle's velocity by a curve sampled at the
7
+ * particle's current `elapsed / lifetime` ratio. The same scalar is applied
8
+ * to both axes — the direction is preserved, only the magnitude scales.
9
+ *
10
+ * Common patterns:
11
+ * - **Snappy spawn, slow drift:** `[0,1]→[1,0.1]` — fast initial motion
12
+ * that decays over lifetime.
13
+ * - **Late acceleration:** `[0,0.2]→[1,1]` — particles ease in.
14
+ * - **Pulse:** sine-shaped curve for breathing-style motion.
15
+ *
16
+ * Note: the curve **replaces** velocity each frame relative to the previous
17
+ * frame's velocity, so the effect is multiplicative. Pair with `ApplyForce`
18
+ * if you want a constant external acceleration on top.
19
+ *
20
+ * GPU-eligible: uploads the curve as a 256-tap 1D R32F texture; sampled
21
+ * once per particle per frame.
22
+ */
23
+ class VelocityOverLifetime extends UpdateModule {
24
+ curve;
25
+ _prevSample = new Float32Array(0);
26
+ constructor(curve) {
27
+ super();
28
+ this.curve = curve;
29
+ }
30
+ apply(system, _dt) {
31
+ const { velX, velY, elapsed, lifetime, liveCount } = system;
32
+ const curve = this.curve;
33
+ // Resize per-particle previous-sample cache once.
34
+ if (this._prevSample.length < system.capacity) {
35
+ this._prevSample = new Float32Array(system.capacity);
36
+ this._prevSample.fill(1);
37
+ }
38
+ const prev = this._prevSample;
39
+ for (let i = 0; i < liveCount; i++) {
40
+ const t = elapsed[i] / lifetime[i];
41
+ const sample = curve.evaluate(t);
42
+ const last = prev[i] === 0 ? 1 : prev[i];
43
+ const delta = sample / last;
44
+ velX[i] *= delta;
45
+ velY[i] *= delta;
46
+ prev[i] = sample === 0 ? 1e-6 : sample;
47
+ }
48
+ }
49
+ wgsl() {
50
+ // GPU path uses a different formulation: re-derive velocity from
51
+ // initial speed at t=0 by storing nothing — instead, scale relative
52
+ // to the previous sample stored in scales[idx] alpha? No — keep it
53
+ // simple and stateless: scale the integrated velocity by the
54
+ // *ratio* of (current sample) / (sample at previous frame's t).
55
+ // We approximate the previous t with `(elapsed - dt) / lifetime`.
56
+ return {
57
+ key: 'VelocityOverLifetime',
58
+ textures: [{ name: 'curve', format: 'r32float' }],
59
+ body: `
60
+ let velLifetime = max(timing[idx].y, 0.000001);
61
+ let velTNow = clamp(timing[idx].x / velLifetime, 0.0, 1.0);
62
+ let velTPrev = clamp((timing[idx].x - dt) / velLifetime, 0.0, 1.0);
63
+ let velSampleNow = textureSampleLevel(u_VelocityOverLifetime_curve, u_VelocityOverLifetime_curve_sampler, velTNow, 0.0).r;
64
+ let velSamplePrev = textureSampleLevel(u_VelocityOverLifetime_curve, u_VelocityOverLifetime_curve_sampler, velTPrev, 0.0).r;
65
+ let velRatio = velSampleNow / max(velSamplePrev, 0.000001);
66
+ velocities[idx] = velocities[idx] * velRatio;
67
+ `,
68
+ };
69
+ }
70
+ uploadTextures(device, textures) {
71
+ const texture = textures.get('curve');
72
+ if (texture === undefined) {
73
+ return;
74
+ }
75
+ const data = new Float32Array(lookupSize);
76
+ for (let i = 0; i < lookupSize; i++) {
77
+ data[i] = this.curve.evaluate(i / (lookupSize - 1));
78
+ }
79
+ device.queue.writeTexture({ texture }, data.buffer, { offset: 0, bytesPerRow: lookupSize * 4, rowsPerImage: 1 }, { width: lookupSize, height: 1, depthOrArrayLayers: 1 });
80
+ }
81
+ }
82
+
83
+ export { VelocityOverLifetime };
84
+ //# sourceMappingURL=VelocityOverLifetime.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"VelocityOverLifetime.js","sources":["../../../../../src/particles/modules/VelocityOverLifetime.ts"],"sourcesContent":[null],"names":[],"mappings":";;AAAA;AAOA,MAAM,UAAU,GAAG,GAAG;AAEtB;;;;;;;;;;;;;;;;;AAiBG;AACG,MAAO,oBAAqB,SAAQ,YAAY,CAAA;AAC3C,IAAA,KAAK;AAEJ,IAAA,WAAW,GAAG,IAAI,YAAY,CAAC,CAAC,CAAC;AAEzC,IAAA,WAAA,CAAmB,KAAY,EAAA;AAC3B,QAAA,KAAK,EAAE;AACP,QAAA,IAAI,CAAC,KAAK,GAAG,KAAK;IACtB;IAEgB,KAAK,CAAC,MAAsB,EAAE,GAAW,EAAA;AACrD,QAAA,MAAM,EAAE,IAAI,EAAE,IAAI,EAAE,OAAO,EAAE,QAAQ,EAAE,SAAS,EAAE,GAAG,MAAM;AAC3D,QAAA,MAAM,KAAK,GAAG,IAAI,CAAC,KAAK;;QAGxB,IAAI,IAAI,CAAC,WAAW,CAAC,MAAM,GAAG,MAAM,CAAC,QAAQ,EAAE;YAC3C,IAAI,CAAC,WAAW,GAAG,IAAI,YAAY,CAAC,MAAM,CAAC,QAAQ,CAAC;AACpD,YAAA,IAAI,CAAC,WAAW,CAAC,IAAI,CAAC,CAAC,CAAC;QAC5B;AAEA,QAAA,MAAM,IAAI,GAAG,IAAI,CAAC,WAAW;AAE7B,QAAA,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,SAAS,EAAE,CAAC,EAAE,EAAE;YAChC,MAAM,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC,GAAG,QAAQ,CAAC,CAAC,CAAC;YAClC,MAAM,MAAM,GAAG,KAAK,CAAC,QAAQ,CAAC,CAAC,CAAC;AAChC,YAAA,MAAM,IAAI,GAAG,IAAI,CAAC,CAAC,CAAC,KAAK,CAAC,GAAG,CAAC,GAAG,IAAI,CAAC,CAAC,CAAC;AACxC,YAAA,MAAM,KAAK,GAAG,MAAM,GAAG,IAAI;AAE3B,YAAA,IAAI,CAAC,CAAC,CAAC,IAAI,KAAK;AAChB,YAAA,IAAI,CAAC,CAAC,CAAC,IAAI,KAAK;AAChB,YAAA,IAAI,CAAC,CAAC,CAAC,GAAG,MAAM,KAAK,CAAC,GAAG,IAAI,GAAG,MAAM;QAC1C;IACJ;IAEgB,IAAI,GAAA;;;;;;;QAOhB,OAAO;AACH,YAAA,GAAG,EAAE,sBAAsB;YAC3B,QAAQ,EAAE,CAAC,EAAE,IAAI,EAAE,OAAO,EAAE,MAAM,EAAE,UAAU,EAAE,CAAC;AACjD,YAAA,IAAI,EAAE;;;;;;;;AAQL,YAAA,CAAA;SACJ;IACL;IAEgB,cAAc,CAAC,MAAiB,EAAE,QAAyC,EAAA;QACvF,MAAM,OAAO,GAAG,QAAQ,CAAC,GAAG,CAAC,OAAO,CAAC;AAErC,QAAA,IAAI,OAAO,KAAK,SAAS,EAAE;YACvB;QACJ;AAEA,QAAA,MAAM,IAAI,GAAG,IAAI,YAAY,CAAC,UAAU,CAAC;AAEzC,QAAA,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,UAAU,EAAE,CAAC,EAAE,EAAE;AACjC,YAAA,IAAI,CAAC,CAAC,CAAC,GAAG,IAAI,CAAC,KAAK,CAAC,QAAQ,CAAC,CAAC,IAAI,UAAU,GAAG,CAAC,CAAC,CAAC;QACvD;AAEA,QAAA,MAAM,CAAC,KAAK,CAAC,YAAY,CACrB,EAAE,OAAO,EAAE,EACX,IAAI,CAAC,MAAqB,EAC1B,EAAE,MAAM,EAAE,CAAC,EAAE,WAAW,EAAE,UAAU,GAAG,CAAC,EAAE,YAAY,EAAE,CAAC,EAAE,EAC3D,EAAE,KAAK,EAAE,UAAU,EAAE,MAAM,EAAE,CAAC,EAAE,kBAAkB,EAAE,CAAC,EAAE,CAC1D;IACL;AACH;;;;"}