@codexo/exojs-particles 0.15.3 → 0.16.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 (211) hide show
  1. package/README.md +21 -43
  2. package/dist/esm/ParticleStorage.d.ts +188 -0
  3. package/dist/esm/ParticleStorage.d.ts.map +1 -0
  4. package/dist/esm/ParticleStorage.js +208 -0
  5. package/dist/esm/ParticleStorage.js.map +1 -0
  6. package/dist/esm/ParticleSystem.d.ts +189 -60
  7. package/dist/esm/ParticleSystem.d.ts.map +1 -0
  8. package/dist/esm/ParticleSystem.js +660 -560
  9. package/dist/esm/ParticleSystem.js.map +1 -1
  10. package/dist/esm/distributions/BoxArea.d.ts +1 -0
  11. package/dist/esm/distributions/BoxArea.d.ts.map +1 -0
  12. package/dist/esm/distributions/BoxArea.js +38 -44
  13. package/dist/esm/distributions/BoxArea.js.map +1 -1
  14. package/dist/esm/distributions/CircleArea.d.ts +1 -0
  15. package/dist/esm/distributions/CircleArea.d.ts.map +1 -0
  16. package/dist/esm/distributions/CircleArea.js +30 -28
  17. package/dist/esm/distributions/CircleArea.js.map +1 -1
  18. package/dist/esm/distributions/ColorGradient.d.ts +3 -2
  19. package/dist/esm/distributions/ColorGradient.d.ts.map +1 -0
  20. package/dist/esm/distributions/ColorGradient.js +70 -75
  21. package/dist/esm/distributions/ColorGradient.js.map +1 -1
  22. package/dist/esm/distributions/ConeDirection.d.ts +2 -1
  23. package/dist/esm/distributions/ConeDirection.d.ts.map +1 -0
  24. package/dist/esm/distributions/ConeDirection.js +41 -39
  25. package/dist/esm/distributions/ConeDirection.js.map +1 -1
  26. package/dist/esm/distributions/Constant.d.ts +1 -0
  27. package/dist/esm/distributions/Constant.d.ts.map +1 -0
  28. package/dist/esm/distributions/Constant.js +32 -32
  29. package/dist/esm/distributions/Constant.js.map +1 -1
  30. package/dist/esm/distributions/Curve.d.ts +2 -1
  31. package/dist/esm/distributions/Curve.d.ts.map +1 -0
  32. package/dist/esm/distributions/Curve.js +46 -55
  33. package/dist/esm/distributions/Curve.js.map +1 -1
  34. package/dist/esm/distributions/Distribution.d.ts +4 -3
  35. package/dist/esm/distributions/Distribution.d.ts.map +1 -0
  36. package/dist/esm/distributions/LineSegment.d.ts +1 -0
  37. package/dist/esm/distributions/LineSegment.d.ts.map +1 -0
  38. package/dist/esm/distributions/LineSegment.js +25 -23
  39. package/dist/esm/distributions/LineSegment.js.map +1 -1
  40. package/dist/esm/distributions/Range.d.ts +1 -0
  41. package/dist/esm/distributions/Range.d.ts.map +1 -0
  42. package/dist/esm/distributions/Range.js +18 -16
  43. package/dist/esm/distributions/Range.js.map +1 -1
  44. package/dist/esm/distributions/VectorRange.d.ts +1 -0
  45. package/dist/esm/distributions/VectorRange.d.ts.map +1 -0
  46. package/dist/esm/distributions/VectorRange.js +29 -27
  47. package/dist/esm/distributions/VectorRange.js.map +1 -1
  48. package/dist/esm/distributions/index.d.ts +1 -0
  49. package/dist/esm/distributions/index.d.ts.map +1 -0
  50. package/dist/esm/gpu/ParticleGpuState.d.ts +143 -18
  51. package/dist/esm/gpu/ParticleGpuState.d.ts.map +1 -0
  52. package/dist/esm/gpu/ParticleGpuState.js +655 -497
  53. package/dist/esm/gpu/ParticleGpuState.js.map +1 -1
  54. package/dist/esm/gpu/shaders/particle-simulate.wgsl.js +6 -0
  55. package/dist/esm/gpu/shaders/particle-simulate.wgsl.js.map +1 -0
  56. package/dist/esm/index.d.ts +1 -0
  57. package/dist/esm/index.d.ts.map +1 -0
  58. package/dist/esm/index.js +39 -32
  59. package/dist/esm/modules/AlphaFadeOverLifetime.d.ts +4 -3
  60. package/dist/esm/modules/AlphaFadeOverLifetime.d.ts.map +1 -0
  61. package/dist/esm/modules/AlphaFadeOverLifetime.js +68 -55
  62. package/dist/esm/modules/AlphaFadeOverLifetime.js.map +1 -1
  63. package/dist/esm/modules/ApplyForce.d.ts +4 -3
  64. package/dist/esm/modules/ApplyForce.d.ts.map +1 -0
  65. package/dist/esm/modules/ApplyForce.js +49 -43
  66. package/dist/esm/modules/ApplyForce.js.map +1 -1
  67. package/dist/esm/modules/AttractToPoint.d.ts +4 -3
  68. package/dist/esm/modules/AttractToPoint.d.ts.map +1 -0
  69. package/dist/esm/modules/AttractToPoint.js +73 -62
  70. package/dist/esm/modules/AttractToPoint.js.map +1 -1
  71. package/dist/esm/modules/BurstSpawn.d.ts +6 -18
  72. package/dist/esm/modules/BurstSpawn.d.ts.map +1 -0
  73. package/dist/esm/modules/BurstSpawn.js +56 -91
  74. package/dist/esm/modules/BurstSpawn.js.map +1 -1
  75. package/dist/esm/modules/ColorOverLifetime.d.ts +4 -3
  76. package/dist/esm/modules/ColorOverLifetime.d.ts.map +1 -0
  77. package/dist/esm/modules/ColorOverLifetime.js +65 -53
  78. package/dist/esm/modules/ColorOverLifetime.js.map +1 -1
  79. package/dist/esm/modules/ColorOverSpeed.d.ts +5 -4
  80. package/dist/esm/modules/ColorOverSpeed.d.ts.map +1 -0
  81. package/dist/esm/modules/ColorOverSpeed.js +90 -75
  82. package/dist/esm/modules/ColorOverSpeed.js.map +1 -1
  83. package/dist/esm/modules/DeathModule.d.ts +26 -10
  84. package/dist/esm/modules/DeathModule.d.ts.map +1 -0
  85. package/dist/esm/modules/DeathModule.js +38 -22
  86. package/dist/esm/modules/DeathModule.js.map +1 -1
  87. package/dist/esm/modules/Drag.d.ts +3 -2
  88. package/dist/esm/modules/Drag.d.ts.map +1 -0
  89. package/dist/esm/modules/Drag.js +43 -37
  90. package/dist/esm/modules/Drag.js.map +1 -1
  91. package/dist/esm/modules/OrbitalForce.d.ts +4 -3
  92. package/dist/esm/modules/OrbitalForce.d.ts.map +1 -0
  93. package/dist/esm/modules/OrbitalForce.js +70 -58
  94. package/dist/esm/modules/OrbitalForce.js.map +1 -1
  95. package/dist/esm/modules/RateSpawn.d.ts +7 -22
  96. package/dist/esm/modules/RateSpawn.d.ts.map +1 -0
  97. package/dist/esm/modules/RateSpawn.js +39 -71
  98. package/dist/esm/modules/RateSpawn.js.map +1 -1
  99. package/dist/esm/modules/RepelFromPoint.d.ts +4 -3
  100. package/dist/esm/modules/RepelFromPoint.d.ts.map +1 -0
  101. package/dist/esm/modules/RepelFromPoint.js +72 -62
  102. package/dist/esm/modules/RepelFromPoint.js.map +1 -1
  103. package/dist/esm/modules/RotateOverLifetime.d.ts +3 -2
  104. package/dist/esm/modules/RotateOverLifetime.d.ts.map +1 -0
  105. package/dist/esm/modules/RotateOverLifetime.js +40 -36
  106. package/dist/esm/modules/RotateOverLifetime.js.map +1 -1
  107. package/dist/esm/modules/ScaleOverLifetime.d.ts +5 -4
  108. package/dist/esm/modules/ScaleOverLifetime.d.ts.map +1 -0
  109. package/dist/esm/modules/ScaleOverLifetime.js +60 -50
  110. package/dist/esm/modules/ScaleOverLifetime.js.map +1 -1
  111. package/dist/esm/modules/SpawnModule.d.ts +15 -12
  112. package/dist/esm/modules/SpawnModule.d.ts.map +1 -0
  113. package/dist/esm/modules/SpawnModule.js +32 -28
  114. package/dist/esm/modules/SpawnModule.js.map +1 -1
  115. package/dist/esm/modules/SpawnOnDeath.d.ts +13 -9
  116. package/dist/esm/modules/SpawnOnDeath.d.ts.map +1 -0
  117. package/dist/esm/modules/SpawnOnDeath.js +41 -43
  118. package/dist/esm/modules/SpawnOnDeath.js.map +1 -1
  119. package/dist/esm/modules/Turbulence.d.ts +4 -3
  120. package/dist/esm/modules/Turbulence.d.ts.map +1 -0
  121. package/dist/esm/modules/Turbulence.js +101 -89
  122. package/dist/esm/modules/Turbulence.js.map +1 -1
  123. package/dist/esm/modules/UpdateModule.d.ts +21 -13
  124. package/dist/esm/modules/UpdateModule.d.ts.map +1 -0
  125. package/dist/esm/modules/UpdateModule.js +65 -63
  126. package/dist/esm/modules/UpdateModule.js.map +1 -1
  127. package/dist/esm/modules/VelocityOverLifetime.d.ts +7 -6
  128. package/dist/esm/modules/VelocityOverLifetime.d.ts.map +1 -0
  129. package/dist/esm/modules/VelocityOverLifetime.js +75 -73
  130. package/dist/esm/modules/VelocityOverLifetime.js.map +1 -1
  131. package/dist/esm/modules/WgslContribution.d.ts +16 -15
  132. package/dist/esm/modules/WgslContribution.d.ts.map +1 -0
  133. package/dist/esm/modules/WgslContribution.js +37 -29
  134. package/dist/esm/modules/WgslContribution.js.map +1 -1
  135. package/dist/esm/modules/index.d.ts +3 -1
  136. package/dist/esm/modules/index.d.ts.map +1 -0
  137. package/dist/esm/modules/spawnFields.d.ts +32 -0
  138. package/dist/esm/modules/spawnFields.d.ts.map +1 -0
  139. package/dist/esm/modules/spawnFields.js +31 -0
  140. package/dist/esm/modules/spawnFields.js.map +1 -0
  141. package/dist/esm/particlesBuildInfo.d.ts +1 -0
  142. package/dist/esm/particlesBuildInfo.d.ts.map +1 -0
  143. package/dist/esm/particlesBuildInfo.js +6 -4
  144. package/dist/esm/particlesBuildInfo.js.map +1 -1
  145. package/dist/esm/particlesExtension.d.ts +13 -5
  146. package/dist/esm/particlesExtension.d.ts.map +1 -0
  147. package/dist/esm/particlesExtension.js +38 -42
  148. package/dist/esm/particlesExtension.js.map +1 -1
  149. package/dist/esm/public.d.ts +11 -0
  150. package/dist/esm/public.d.ts.map +1 -0
  151. package/dist/esm/renderModes/MeshParticles.d.ts +128 -0
  152. package/dist/esm/renderModes/MeshParticles.d.ts.map +1 -0
  153. package/dist/esm/renderModes/MeshParticles.js +263 -0
  154. package/dist/esm/renderModes/MeshParticles.js.map +1 -0
  155. package/dist/esm/renderModes/ParticleBufferLayout.d.ts +59 -0
  156. package/dist/esm/renderModes/ParticleBufferLayout.d.ts.map +1 -0
  157. package/dist/esm/renderModes/ParticleBufferLayout.js +99 -0
  158. package/dist/esm/renderModes/ParticleBufferLayout.js.map +1 -0
  159. package/dist/esm/renderModes/ParticleInstanceWriter.d.ts +58 -0
  160. package/dist/esm/renderModes/ParticleInstanceWriter.d.ts.map +1 -0
  161. package/dist/esm/renderModes/ParticleInstanceWriter.js +173 -0
  162. package/dist/esm/renderModes/ParticleInstanceWriter.js.map +1 -0
  163. package/dist/esm/renderModes/ParticleMaterial.d.ts +18 -0
  164. package/dist/esm/renderModes/ParticleMaterial.d.ts.map +1 -0
  165. package/dist/esm/renderModes/ParticleMaterial.js +24 -0
  166. package/dist/esm/renderModes/ParticleMaterial.js.map +1 -0
  167. package/dist/esm/renderModes/ParticleRenderMode.d.ts +114 -0
  168. package/dist/esm/renderModes/ParticleRenderMode.d.ts.map +1 -0
  169. package/dist/esm/renderModes/ParticleRenderMode.js +99 -0
  170. package/dist/esm/renderModes/ParticleRenderMode.js.map +1 -0
  171. package/dist/esm/renderModes/QuadParticles.d.ts +55 -0
  172. package/dist/esm/renderModes/QuadParticles.d.ts.map +1 -0
  173. package/dist/esm/renderModes/QuadParticles.js +95 -0
  174. package/dist/esm/renderModes/QuadParticles.js.map +1 -0
  175. package/dist/esm/renderModes/RibbonParticles.d.ts +96 -0
  176. package/dist/esm/renderModes/RibbonParticles.d.ts.map +1 -0
  177. package/dist/esm/renderModes/RibbonParticles.js +202 -0
  178. package/dist/esm/renderModes/RibbonParticles.js.map +1 -0
  179. package/dist/esm/renderModes/shaders/mesh-particles.wgsl.js +6 -0
  180. package/dist/esm/renderModes/shaders/mesh-particles.wgsl.js.map +1 -0
  181. package/dist/esm/renderModes/shaders/mesh.vert.js +6 -0
  182. package/dist/esm/renderModes/shaders/mesh.vert.js.map +1 -0
  183. package/dist/esm/renderModes/shaders/quad-particles.wgsl.js +6 -0
  184. package/dist/esm/renderModes/shaders/quad-particles.wgsl.js.map +1 -0
  185. package/dist/esm/renderModes/shaders/ribbon-particles.wgsl.js +6 -0
  186. package/dist/esm/renderModes/shaders/ribbon-particles.wgsl.js.map +1 -0
  187. package/dist/esm/renderModes/shaders/ribbon.frag.js +6 -0
  188. package/dist/esm/renderModes/shaders/ribbon.frag.js.map +1 -0
  189. package/dist/esm/renderModes/shaders/ribbon.vert.js +6 -0
  190. package/dist/esm/renderModes/shaders/ribbon.vert.js.map +1 -0
  191. package/dist/esm/renderers/WebGl2ParticleRenderer.d.ts +48 -20
  192. package/dist/esm/renderers/WebGl2ParticleRenderer.d.ts.map +1 -0
  193. package/dist/esm/renderers/WebGl2ParticleRenderer.js +317 -321
  194. package/dist/esm/renderers/WebGl2ParticleRenderer.js.map +1 -1
  195. package/dist/esm/renderers/WebGpuParticleRenderer.d.ts +99 -18
  196. package/dist/esm/renderers/WebGpuParticleRenderer.d.ts.map +1 -0
  197. package/dist/esm/renderers/WebGpuParticleRenderer.js +542 -551
  198. package/dist/esm/renderers/WebGpuParticleRenderer.js.map +1 -1
  199. package/dist/esm/renderers/shaders/particle.frag.js +6 -0
  200. package/dist/esm/renderers/shaders/particle.frag.js.map +1 -0
  201. package/dist/esm/renderers/shaders/particle.vert.js +6 -0
  202. package/dist/esm/renderers/shaders/particle.vert.js.map +1 -0
  203. package/package.json +8 -15
  204. package/dist/esm/index.js.map +0 -1
  205. package/dist/esm/register.d.ts +0 -1
  206. package/dist/esm/register.js +0 -43
  207. package/dist/esm/register.js.map +0 -1
  208. package/dist/esm/renderers/glsl/particle.frag.js +0 -4
  209. package/dist/esm/renderers/glsl/particle.frag.js.map +0 -1
  210. package/dist/esm/renderers/glsl/particle.vert.js +0 -4
  211. package/dist/esm/renderers/glsl/particle.vert.js.map +0 -1
@@ -1,5 +1,5 @@
1
- import type { ColorGradient } from "#distributions/ColorGradient";
2
- import type { ParticleSystem } from "#ParticleSystem";
1
+ import type { ColorGradient } from '#distributions/ColorGradient';
2
+ import type { ParticleBatch } from '#ParticleStorage';
3
3
  import { UpdateModule } from './UpdateModule';
4
4
  import type { WgslContribution } from './WgslContribution';
5
5
  /**
@@ -11,7 +11,7 @@ import type { WgslContribution } from './WgslContribution';
11
11
  * speed-gated highlights.
12
12
  *
13
13
  * GPU-eligible: gradient uploaded as a 256-tap 1D RGBA8 texture, sampled
14
- * with linear filtering. Replaces the full color word pair with a
14
+ * with linear filtering. Replaces the full color word - pair with a
15
15
  * separate {@link AlphaFadeOverLifetime} after this module if you want to
16
16
  * keep alpha controlled by lifetime.
17
17
  */
@@ -20,8 +20,9 @@ export declare class ColorOverSpeed extends UpdateModule {
20
20
  minSpeed: number;
21
21
  maxSpeed: number;
22
22
  constructor(gradient: ColorGradient, minSpeed: number, maxSpeed: number);
23
- apply(system: ParticleSystem, _dt: number): void;
23
+ apply(particles: ParticleBatch, _dt: number): void;
24
24
  wgsl(): WgslContribution;
25
25
  writeUniforms(view: DataView, offset: number): void;
26
26
  uploadTextures(device: GPUDevice, textures: ReadonlyMap<string, GPUTexture>): void;
27
27
  }
28
+ //# sourceMappingURL=ColorOverSpeed.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ColorOverSpeed.d.ts","sourceRoot":"","sources":["../../../src/modules/ColorOverSpeed.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,8BAA8B,CAAC;AAClE,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,kBAAkB,CAAC;AAEtD,OAAO,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAC;AAC9C,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,oBAAoB,CAAC;AAI3D;;;;;;;;;;;;GAYG;AACH,qBAAa,cAAe,SAAQ,YAAY;IACvC,QAAQ,EAAE,aAAa,CAAC;IACxB,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE,MAAM,CAAC;gBAEL,QAAQ,EAAE,aAAa,EAAE,QAAQ,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM;IAO9D,KAAK,CAAC,SAAS,EAAE,aAAa,EAAE,GAAG,EAAE,MAAM,GAAG,IAAI;IAkBlD,IAAI,IAAI,gBAAgB;IAqBxB,aAAa,CAAC,IAAI,EAAE,QAAQ,EAAE,MAAM,EAAE,MAAM,GAAG,IAAI;IAOnD,cAAc,CAAC,MAAM,EAAE,SAAS,EAAE,QAAQ,EAAE,WAAW,CAAC,MAAM,EAAE,UAAU,CAAC,GAAG,IAAI;CA8BnG"}
@@ -1,53 +1,61 @@
1
- import { Color } from '@codexo/exojs';
2
- import { UpdateModule } from './UpdateModule.js';
1
+ import { UpdateModule } from "./UpdateModule.js";
2
+ import { Color } from "@codexo/exojs";
3
3
 
4
- /// <reference types="@webgpu/types" />
4
+ //#region src/modules/ColorOverSpeed.ts
5
5
  const lookupSize = 256;
6
6
  /**
7
- * Per-frame, per-particle color sampler driven by velocity magnitude rather
8
- * than lifetime ratio. Each live particle's tint is set to the gradient
9
- * evaluated at `clamp((|velocity| - minSpeed) / (maxSpeed - minSpeed), 0, 1)`.
10
- *
11
- * Use cases: heat-mapping (slow=blue, fast=red), velocity-tinted trails,
12
- * speed-gated highlights.
13
- *
14
- * GPU-eligible: gradient uploaded as a 256-tap 1D RGBA8 texture, sampled
15
- * with linear filtering. Replaces the full color word pair with a
16
- * separate {@link AlphaFadeOverLifetime} after this module if you want to
17
- * keep alpha controlled by lifetime.
18
- */
19
- class ColorOverSpeed extends UpdateModule {
20
- gradient;
21
- minSpeed;
22
- maxSpeed;
23
- constructor(gradient, minSpeed, maxSpeed) {
24
- super();
25
- this.gradient = gradient;
26
- this.minSpeed = minSpeed;
27
- this.maxSpeed = maxSpeed;
28
- }
29
- apply(system, _dt) {
30
- const { velX, velY, color, liveCount } = system;
31
- const gradient = this.gradient;
32
- const min = this.minSpeed;
33
- const span = Math.max(1e-5, this.maxSpeed - this.minSpeed);
34
- for (let i = 0; i < liveCount; i++) {
35
- const vx = velX[i] ?? 0;
36
- const vy = velY[i] ?? 0;
37
- const speed = Math.sqrt(vx * vx + vy * vy);
38
- const t = Math.max(0, Math.min(1, (speed - min) / span));
39
- color[i] = gradient.evaluateRgba(t);
40
- }
41
- }
42
- wgsl() {
43
- return {
44
- key: 'ColorOverSpeed',
45
- uniforms: [
46
- { name: 'minSpeed', type: 'f32' },
47
- { name: 'invSpan', type: 'f32' },
48
- ],
49
- textures: [{ name: 'gradient', format: 'rgba8unorm' }],
50
- body: `
7
+ * Per-frame, per-particle color sampler driven by velocity magnitude rather
8
+ * than lifetime ratio. Each live particle's tint is set to the gradient
9
+ * evaluated at `clamp((|velocity| - minSpeed) / (maxSpeed - minSpeed), 0, 1)`.
10
+ *
11
+ * Use cases: heat-mapping (slow=blue, fast=red), velocity-tinted trails,
12
+ * speed-gated highlights.
13
+ *
14
+ * GPU-eligible: gradient uploaded as a 256-tap 1D RGBA8 texture, sampled
15
+ * with linear filtering. Replaces the full color word - pair with a
16
+ * separate {@link AlphaFadeOverLifetime} after this module if you want to
17
+ * keep alpha controlled by lifetime.
18
+ */
19
+ var ColorOverSpeed = class extends UpdateModule {
20
+ gradient;
21
+ minSpeed;
22
+ maxSpeed;
23
+ constructor(gradient, minSpeed, maxSpeed) {
24
+ super();
25
+ this.gradient = gradient;
26
+ this.minSpeed = minSpeed;
27
+ this.maxSpeed = maxSpeed;
28
+ }
29
+ apply(particles, _dt) {
30
+ const { x: velX, y: velY } = particles.velocity;
31
+ const color = particles.color;
32
+ const liveCount = particles.count;
33
+ const gradient = this.gradient;
34
+ const min = this.minSpeed;
35
+ const span = Math.max(1e-5, this.maxSpeed - this.minSpeed);
36
+ for (let i = 0; i < liveCount; i++) {
37
+ const vx = velX[i] ?? 0;
38
+ const vy = velY[i] ?? 0;
39
+ const speed = Math.sqrt(vx * vx + vy * vy);
40
+ const t = Math.max(0, Math.min(1, (speed - min) / span));
41
+ color[i] = gradient.evaluateRgba(t);
42
+ }
43
+ }
44
+ wgsl() {
45
+ return {
46
+ key: "ColorOverSpeed",
47
+ uniforms: [{
48
+ name: "minSpeed",
49
+ type: "f32"
50
+ }, {
51
+ name: "invSpan",
52
+ type: "f32"
53
+ }],
54
+ textures: [{
55
+ name: "gradient",
56
+ format: "rgba8unorm"
57
+ }],
58
+ body: `
51
59
  let speedMag = length(velocities[idx]);
52
60
  let speedT = clamp((speedMag - modules.u_ColorOverSpeed.minSpeed) * modules.u_ColorOverSpeed.invSpan, 0.0, 1.0);
53
61
  let speedSample = textureSampleLevel(u_ColorOverSpeed_gradient, u_ColorOverSpeed_gradient_sampler, speedT, 0.0);
@@ -56,33 +64,40 @@ class ColorOverSpeed extends UpdateModule {
56
64
  let speedB = u32(speedSample.b * 255.0) & 255u;
57
65
  let speedA = u32(speedSample.a * 255.0) & 255u;
58
66
  color[idx] = (speedA << 24u) | (speedB << 16u) | (speedG << 8u) | speedR;
59
- `,
60
- };
61
- }
62
- writeUniforms(view, offset) {
63
- const span = Math.max(1e-5, this.maxSpeed - this.minSpeed);
64
- view.setFloat32(offset + 0, this.minSpeed, true);
65
- view.setFloat32(offset + 4, 1 / span, true);
66
- }
67
- uploadTextures(device, textures) {
68
- const texture = textures.get('gradient');
69
- if (texture === undefined) {
70
- return;
71
- }
72
- const data = new Uint8Array(lookupSize * 4);
73
- const scratch = new Color();
74
- for (let i = 0; i < lookupSize; i++) {
75
- const t = i / (lookupSize - 1);
76
- this.gradient.evaluate(t, scratch);
77
- const o = i * 4;
78
- data[o + 0] = scratch.r & 255;
79
- data[o + 1] = scratch.g & 255;
80
- data[o + 2] = scratch.b & 255;
81
- data[o + 3] = ((scratch.a * 255) | 0) & 255;
82
- }
83
- device.queue.writeTexture({ texture }, data.buffer, { offset: 0, bytesPerRow: lookupSize * 4, rowsPerImage: 1 }, { width: lookupSize, height: 1, depthOrArrayLayers: 1 });
84
- }
85
- }
67
+ `
68
+ };
69
+ }
70
+ writeUniforms(view, offset) {
71
+ const span = Math.max(1e-5, this.maxSpeed - this.minSpeed);
72
+ view.setFloat32(offset + 0, this.minSpeed, true);
73
+ view.setFloat32(offset + 4, 1 / span, true);
74
+ }
75
+ uploadTextures(device, textures) {
76
+ const texture = textures.get("gradient");
77
+ if (texture === void 0) return;
78
+ const data = new Uint8Array(lookupSize * 4);
79
+ const scratch = new Color();
80
+ for (let i = 0; i < lookupSize; i++) {
81
+ const t = i / 255;
82
+ this.gradient.evaluate(t, scratch);
83
+ const o = i * 4;
84
+ data[o + 0] = scratch.r & 255;
85
+ data[o + 1] = scratch.g & 255;
86
+ data[o + 2] = scratch.b & 255;
87
+ data[o + 3] = (scratch.a * 255 | 0) & 255;
88
+ }
89
+ device.queue.writeTexture({ texture }, data.buffer, {
90
+ offset: 0,
91
+ bytesPerRow: lookupSize * 4,
92
+ rowsPerImage: 1
93
+ }, {
94
+ width: lookupSize,
95
+ height: 1,
96
+ depthOrArrayLayers: 1
97
+ });
98
+ }
99
+ };
86
100
 
101
+ //#endregion
87
102
  export { ColorOverSpeed };
88
- //# sourceMappingURL=ColorOverSpeed.js.map
103
+ //# sourceMappingURL=ColorOverSpeed.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"ColorOverSpeed.js","sources":["../../../../src/modules/ColorOverSpeed.ts"],"sourcesContent":[null],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"ColorOverSpeed.js","names":[],"sources":["../../../src/modules/ColorOverSpeed.ts"],"sourcesContent":["/// <reference types=\"@webgpu/types\" />\n\nimport { Color } from '@codexo/exojs';\n\nimport type { ColorGradient } from '#distributions/ColorGradient';\nimport type { ParticleBatch } from '#ParticleStorage';\n\nimport { UpdateModule } from './UpdateModule';\nimport type { WgslContribution } from './WgslContribution';\n\nconst lookupSize = 256;\n\n/**\n * Per-frame, per-particle color sampler driven by velocity magnitude rather\n * than lifetime ratio. Each live particle's tint is set to the gradient\n * evaluated at `clamp((|velocity| - minSpeed) / (maxSpeed - minSpeed), 0, 1)`.\n *\n * Use cases: heat-mapping (slow=blue, fast=red), velocity-tinted trails,\n * speed-gated highlights.\n *\n * GPU-eligible: gradient uploaded as a 256-tap 1D RGBA8 texture, sampled\n * with linear filtering. Replaces the full color word - pair with a\n * separate {@link AlphaFadeOverLifetime} after this module if you want to\n * keep alpha controlled by lifetime.\n */\nexport class ColorOverSpeed extends UpdateModule {\n public gradient: ColorGradient;\n public minSpeed: number;\n public maxSpeed: number;\n\n public constructor(gradient: ColorGradient, minSpeed: number, maxSpeed: number) {\n super();\n this.gradient = gradient;\n this.minSpeed = minSpeed;\n this.maxSpeed = maxSpeed;\n }\n\n public override apply(particles: ParticleBatch, _dt: number): void {\n const { x: velX, y: velY } = particles.velocity;\n const color = particles.color;\n const liveCount = particles.count;\n const gradient = this.gradient;\n const min = this.minSpeed;\n const span = Math.max(1e-5, this.maxSpeed - this.minSpeed);\n\n for (let i = 0; i < liveCount; i++) {\n const vx = velX[i] ?? 0;\n const vy = velY[i] ?? 0;\n const speed = Math.sqrt(vx * vx + vy * vy);\n const t = Math.max(0, Math.min(1, (speed - min) / span));\n\n color[i] = gradient.evaluateRgba(t);\n }\n }\n\n public override wgsl(): WgslContribution {\n return {\n key: 'ColorOverSpeed',\n uniforms: [\n { name: 'minSpeed', type: 'f32' },\n { name: 'invSpan', type: 'f32' },\n ],\n textures: [{ name: 'gradient', format: 'rgba8unorm' }],\n body: `\n let speedMag = length(velocities[idx]);\n let speedT = clamp((speedMag - modules.u_ColorOverSpeed.minSpeed) * modules.u_ColorOverSpeed.invSpan, 0.0, 1.0);\n let speedSample = textureSampleLevel(u_ColorOverSpeed_gradient, u_ColorOverSpeed_gradient_sampler, speedT, 0.0);\n let speedR = u32(speedSample.r * 255.0) & 255u;\n let speedG = u32(speedSample.g * 255.0) & 255u;\n let speedB = u32(speedSample.b * 255.0) & 255u;\n let speedA = u32(speedSample.a * 255.0) & 255u;\n color[idx] = (speedA << 24u) | (speedB << 16u) | (speedG << 8u) | speedR;\n `,\n };\n }\n\n public override writeUniforms(view: DataView, offset: number): void {\n const span = Math.max(1e-5, this.maxSpeed - this.minSpeed);\n\n view.setFloat32(offset + 0, this.minSpeed, true);\n view.setFloat32(offset + 4, 1 / span, true);\n }\n\n public override uploadTextures(device: GPUDevice, textures: ReadonlyMap<string, GPUTexture>): void {\n const texture = textures.get('gradient');\n\n if (texture === undefined) {\n return;\n }\n\n const data = new Uint8Array(lookupSize * 4);\n const scratch = new Color();\n\n for (let i = 0; i < lookupSize; i++) {\n const t = i / (lookupSize - 1);\n\n this.gradient.evaluate(t, scratch);\n\n const o = i * 4;\n\n data[o + 0] = scratch.r & 255;\n data[o + 1] = scratch.g & 255;\n data[o + 2] = scratch.b & 255;\n data[o + 3] = ((scratch.a * 255) | 0) & 255;\n }\n\n device.queue.writeTexture(\n { texture },\n data.buffer,\n { offset: 0, bytesPerRow: lookupSize * 4, rowsPerImage: 1 },\n { width: lookupSize, height: 1, depthOrArrayLayers: 1 },\n );\n }\n}\n"],"mappings":";;;;AAUA,MAAM,aAAa;;;;;;;;;;;;;;AAenB,IAAa,iBAAb,cAAoC,aAAa;CAC/C,AAAO;CACP,AAAO;CACP,AAAO;CAEP,AAAO,YAAY,UAAyB,UAAkB,UAAkB;EAC9E,MAAM;EACN,KAAK,WAAW;EAChB,KAAK,WAAW;EAChB,KAAK,WAAW;CAClB;CAEA,AAAgB,MAAM,WAA0B,KAAmB;EACjE,MAAM,EAAE,GAAG,MAAM,GAAG,SAAS,UAAU;EACvC,MAAM,QAAQ,UAAU;EACxB,MAAM,YAAY,UAAU;EAC5B,MAAM,WAAW,KAAK;EACtB,MAAM,MAAM,KAAK;EACjB,MAAM,OAAO,KAAK,IAAI,MAAM,KAAK,WAAW,KAAK,QAAQ;EAEzD,KAAK,IAAI,IAAI,GAAG,IAAI,WAAW,KAAK;GAClC,MAAM,KAAK,KAAK,MAAM;GACtB,MAAM,KAAK,KAAK,MAAM;GACtB,MAAM,QAAQ,KAAK,KAAK,KAAK,KAAK,KAAK,EAAE;GACzC,MAAM,IAAI,KAAK,IAAI,GAAG,KAAK,IAAI,IAAI,QAAQ,OAAO,IAAI,CAAC;GAEvD,MAAM,KAAK,SAAS,aAAa,CAAC;EACpC;CACF;CAEA,AAAgB,OAAyB;EACvC,OAAO;GACL,KAAK;GACL,UAAU,CACR;IAAE,MAAM;IAAY,MAAM;GAAM,GAChC;IAAE,MAAM;IAAW,MAAM;GAAM,CACjC;GACA,UAAU,CAAC;IAAE,MAAM;IAAY,QAAQ;GAAa,CAAC;GACrD,MAAM;;;;;;;;;;EAUR;CACF;CAEA,AAAgB,cAAc,MAAgB,QAAsB;EAClE,MAAM,OAAO,KAAK,IAAI,MAAM,KAAK,WAAW,KAAK,QAAQ;EAEzD,KAAK,WAAW,SAAS,GAAG,KAAK,UAAU,IAAI;EAC/C,KAAK,WAAW,SAAS,GAAG,IAAI,MAAM,IAAI;CAC5C;CAEA,AAAgB,eAAe,QAAmB,UAAiD;EACjG,MAAM,UAAU,SAAS,IAAI,UAAU;EAEvC,IAAI,YAAY,QACd;EAGF,MAAM,OAAO,IAAI,WAAW,aAAa,CAAC;EAC1C,MAAM,UAAU,IAAI,MAAM;EAE1B,KAAK,IAAI,IAAI,GAAG,IAAI,YAAY,KAAK;GACnC,MAAM,IAAI,IAAK;GAEf,KAAK,SAAS,SAAS,GAAG,OAAO;GAEjC,MAAM,IAAI,IAAI;GAEd,KAAK,IAAI,KAAK,QAAQ,IAAI;GAC1B,KAAK,IAAI,KAAK,QAAQ,IAAI;GAC1B,KAAK,IAAI,KAAK,QAAQ,IAAI;GAC1B,KAAK,IAAI,MAAO,QAAQ,IAAI,MAAO,KAAK;EAC1C;EAEA,OAAO,MAAM,aACX,EAAE,QAAQ,GACV,KAAK,QACL;GAAE,QAAQ;GAAG,aAAa,aAAa;GAAG,cAAc;EAAE,GAC1D;GAAE,OAAO;GAAY,QAAQ;GAAG,oBAAoB;EAAE,CACxD;CACF;AACF"}
@@ -1,24 +1,40 @@
1
- import type { ParticleSystem } from "#ParticleSystem";
1
+ import type { ParticleDeathContext } from '#ParticleStorage';
2
+ import type { ParticleSystem } from '#ParticleSystem';
2
3
  /**
3
- * Per-particle hook invoked exactly once when a particle expires, before
4
- * its slot is recycled by the compaction pass. The dying particle's data
5
- * is still readable at `system.posX[slot]` etc.
4
+ * Per-particle hook invoked exactly once after a particle expires, carrying its
5
+ * state at the moment it died.
6
6
  *
7
7
  * Use for sub-emitters (spawn child particles where this one died), event
8
- * dispatch (trigger an audio cue, score event), or trail termination.
8
+ * dispatch (trigger an audio cue, a score event), or trail termination.
9
+ *
10
+ * The callback receives a snapshot rather than a slot, and for a reason: on the
11
+ * GPU path the simulation runs on the device and a death is reported once its
12
+ * state has been read back, by which time the slot may already hold a different
13
+ * particle. The snapshot is therefore the same on both backends, while a slot
14
+ * would not be.
15
+ *
16
+ * Delivery is exactly once per expired particle, but not necessarily in the
17
+ * frame the particle expired: a GPU-simulated death arrives as soon as its
18
+ * readback lands, typically the next frame. Deaths from one readback are
19
+ * delivered in slot order, and readbacks in the order they were submitted.
20
+ *
21
+ * Exactly-once holds while the deaths waiting on a readback fit the system's
22
+ * capacity. Beyond that the excess is dropped rather than stalling the frame
23
+ * loop, and a development build reports it once per system. Reaching that point
24
+ * takes a device that falls many frames behind while slots are recycled and die
25
+ * again, so a system sized for its emission rate does not see it.
9
26
  *
10
27
  * Implementation pattern:
11
28
  *
12
29
  * ```ts
13
- * onDeath(system, slot) {
14
- * const x = system.posX[slot];
15
- * const y = system.posY[slot];
16
- * this._childSystem.spawnBurstAt(x, y, 8);
30
+ * onDeath(system, death) {
31
+ * this._childSystem.burstAt(death.x, death.y, 8);
17
32
  * }
18
33
  * ```
19
34
  */
20
35
  export declare abstract class DeathModule {
21
- abstract onDeath(system: ParticleSystem, slot: number): void;
36
+ abstract onDeath(system: ParticleSystem, death: ParticleDeathContext): void;
22
37
  /** Optional cleanup hook called from `ParticleSystem.destroy`. */
23
38
  destroy(): void;
24
39
  }
40
+ //# sourceMappingURL=DeathModule.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"DeathModule.d.ts","sourceRoot":"","sources":["../../../src/modules/DeathModule.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,kBAAkB,CAAC;AAC7D,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,iBAAiB,CAAC;AAEtD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+BG;AACH,8BAAsB,WAAW;aACf,OAAO,CAAC,MAAM,EAAE,cAAc,EAAE,KAAK,EAAE,oBAAoB,GAAG,IAAI;IAClF,kEAAkE;IAC3D,OAAO,IAAI,IAAI;CACvB"}
@@ -1,25 +1,41 @@
1
+ //#region src/modules/DeathModule.ts
1
2
  /**
2
- * Per-particle hook invoked exactly once when a particle expires, before
3
- * its slot is recycled by the compaction pass. The dying particle's data
4
- * is still readable at `system.posX[slot]` etc.
5
- *
6
- * Use for sub-emitters (spawn child particles where this one died), event
7
- * dispatch (trigger an audio cue, score event), or trail termination.
8
- *
9
- * Implementation pattern:
10
- *
11
- * ```ts
12
- * onDeath(system, slot) {
13
- * const x = system.posX[slot];
14
- * const y = system.posY[slot];
15
- * this._childSystem.spawnBurstAt(x, y, 8);
16
- * }
17
- * ```
18
- */
19
- class DeathModule {
20
- /** Optional cleanup hook called from `ParticleSystem.destroy`. */
21
- destroy() { }
22
- }
3
+ * Per-particle hook invoked exactly once after a particle expires, carrying its
4
+ * state at the moment it died.
5
+ *
6
+ * Use for sub-emitters (spawn child particles where this one died), event
7
+ * dispatch (trigger an audio cue, a score event), or trail termination.
8
+ *
9
+ * The callback receives a snapshot rather than a slot, and for a reason: on the
10
+ * GPU path the simulation runs on the device and a death is reported once its
11
+ * state has been read back, by which time the slot may already hold a different
12
+ * particle. The snapshot is therefore the same on both backends, while a slot
13
+ * would not be.
14
+ *
15
+ * Delivery is exactly once per expired particle, but not necessarily in the
16
+ * frame the particle expired: a GPU-simulated death arrives as soon as its
17
+ * readback lands, typically the next frame. Deaths from one readback are
18
+ * delivered in slot order, and readbacks in the order they were submitted.
19
+ *
20
+ * Exactly-once holds while the deaths waiting on a readback fit the system's
21
+ * capacity. Beyond that the excess is dropped rather than stalling the frame
22
+ * loop, and a development build reports it once per system. Reaching that point
23
+ * takes a device that falls many frames behind while slots are recycled and die
24
+ * again, so a system sized for its emission rate does not see it.
25
+ *
26
+ * Implementation pattern:
27
+ *
28
+ * ```ts
29
+ * onDeath(system, death) {
30
+ * this._childSystem.burstAt(death.x, death.y, 8);
31
+ * }
32
+ * ```
33
+ */
34
+ var DeathModule = class {
35
+ /** Optional cleanup hook called from `ParticleSystem.destroy`. */
36
+ destroy() {}
37
+ };
23
38
 
39
+ //#endregion
24
40
  export { DeathModule };
25
- //# sourceMappingURL=DeathModule.js.map
41
+ //# sourceMappingURL=DeathModule.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"DeathModule.js","sources":["../../../../src/modules/DeathModule.ts"],"sourcesContent":[null],"names":[],"mappings":"AAEA;;;;;;;;;;;;;;;;;AAiBG;MACmB,WAAW,CAAA;;AAGxB,IAAA,OAAO,KAAU;AACzB;;;;"}
1
+ {"version":3,"file":"DeathModule.js","names":[],"sources":["../../../src/modules/DeathModule.ts"],"sourcesContent":["import type { ParticleDeathContext } from '#ParticleStorage';\nimport type { ParticleSystem } from '#ParticleSystem';\n\n/**\n * Per-particle hook invoked exactly once after a particle expires, carrying its\n * state at the moment it died.\n *\n * Use for sub-emitters (spawn child particles where this one died), event\n * dispatch (trigger an audio cue, a score event), or trail termination.\n *\n * The callback receives a snapshot rather than a slot, and for a reason: on the\n * GPU path the simulation runs on the device and a death is reported once its\n * state has been read back, by which time the slot may already hold a different\n * particle. The snapshot is therefore the same on both backends, while a slot\n * would not be.\n *\n * Delivery is exactly once per expired particle, but not necessarily in the\n * frame the particle expired: a GPU-simulated death arrives as soon as its\n * readback lands, typically the next frame. Deaths from one readback are\n * delivered in slot order, and readbacks in the order they were submitted.\n *\n * Exactly-once holds while the deaths waiting on a readback fit the system's\n * capacity. Beyond that the excess is dropped rather than stalling the frame\n * loop, and a development build reports it once per system. Reaching that point\n * takes a device that falls many frames behind while slots are recycled and die\n * again, so a system sized for its emission rate does not see it.\n *\n * Implementation pattern:\n *\n * ```ts\n * onDeath(system, death) {\n * this._childSystem.burstAt(death.x, death.y, 8);\n * }\n * ```\n */\nexport abstract class DeathModule {\n public abstract onDeath(system: ParticleSystem, death: ParticleDeathContext): void;\n /** Optional cleanup hook called from `ParticleSystem.destroy`. */\n public destroy(): void {}\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAmCA,IAAsB,cAAtB,MAAkC;;CAGhC,AAAO,UAAgB,CAAC;AAC1B"}
@@ -1,4 +1,4 @@
1
- import type { ParticleSystem } from "#ParticleSystem";
1
+ import type { ParticleBatch } from '#ParticleStorage';
2
2
  import { UpdateModule } from './UpdateModule';
3
3
  import type { WgslContribution } from './WgslContribution';
4
4
  /**
@@ -14,7 +14,8 @@ import type { WgslContribution } from './WgslContribution';
14
14
  export declare class Drag extends UpdateModule {
15
15
  drag: number;
16
16
  constructor(drag: number);
17
- apply(system: ParticleSystem, dt: number): void;
17
+ apply(particles: ParticleBatch, dt: number): void;
18
18
  wgsl(): WgslContribution;
19
19
  writeUniforms(view: DataView, offset: number): void;
20
20
  }
21
+ //# sourceMappingURL=Drag.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Drag.d.ts","sourceRoot":"","sources":["../../../src/modules/Drag.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,kBAAkB,CAAC;AAEtD,OAAO,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAC;AAC9C,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,oBAAoB,CAAC;AAE3D;;;;;;;;;GASG;AACH,qBAAa,IAAK,SAAQ,YAAY;IAC7B,IAAI,EAAE,MAAM,CAAC;gBAED,IAAI,EAAE,MAAM;IAKf,KAAK,CAAC,SAAS,EAAE,aAAa,EAAE,EAAE,EAAE,MAAM,GAAG,IAAI;IAWjD,IAAI,IAAI,gBAAgB;IAWxB,aAAa,CAAC,IAAI,EAAE,QAAQ,EAAE,MAAM,EAAE,MAAM,GAAG,IAAI;CAGpE"}
@@ -1,43 +1,49 @@
1
- import { UpdateModule } from './UpdateModule.js';
1
+ import { UpdateModule } from "./UpdateModule.js";
2
2
 
3
+ //#region src/modules/Drag.ts
3
4
  /**
4
- * Exponential velocity damping. Each frame multiplies every live particle's
5
- * velocity by `(1 - drag * dt)`, simulating linear air resistance.
6
- *
7
- * `drag = 0` is no damping; `drag = 1` halves velocity in ~1 second; higher
8
- * values slow particles faster. Negative values accelerate (don't do that
9
- * unless you mean it).
10
- *
11
- * GPU-eligible.
12
- */
13
- class Drag extends UpdateModule {
14
- drag;
15
- constructor(drag) {
16
- super();
17
- this.drag = drag;
18
- }
19
- apply(system, dt) {
20
- const { velX, velY, liveCount } = system;
21
- const factor = 1 - this.drag * dt;
22
- for (let i = 0; i < liveCount; i++) {
23
- velX[i] = (velX[i] ?? 0) * factor;
24
- velY[i] = (velY[i] ?? 0) * factor;
25
- }
26
- }
27
- wgsl() {
28
- return {
29
- key: 'Drag',
30
- uniforms: [{ name: 'drag', type: 'f32' }],
31
- body: `
5
+ * Exponential velocity damping. Each frame multiplies every live particle's
6
+ * velocity by `(1 - drag * dt)`, simulating linear air resistance.
7
+ *
8
+ * `drag = 0` is no damping; `drag = 1` halves velocity in ~1 second; higher
9
+ * values slow particles faster. Negative values accelerate (don't do that
10
+ * unless you mean it).
11
+ *
12
+ * GPU-eligible.
13
+ */
14
+ var Drag = class extends UpdateModule {
15
+ drag;
16
+ constructor(drag) {
17
+ super();
18
+ this.drag = drag;
19
+ }
20
+ apply(particles, dt) {
21
+ const { x: velX, y: velY } = particles.velocity;
22
+ const liveCount = particles.count;
23
+ const factor = 1 - this.drag * dt;
24
+ for (let i = 0; i < liveCount; i++) {
25
+ velX[i] = (velX[i] ?? 0) * factor;
26
+ velY[i] = (velY[i] ?? 0) * factor;
27
+ }
28
+ }
29
+ wgsl() {
30
+ return {
31
+ key: "Drag",
32
+ uniforms: [{
33
+ name: "drag",
34
+ type: "f32"
35
+ }],
36
+ body: `
32
37
  let dragFactor = 1.0 - modules.u_Drag.drag * dt;
33
38
  velocities[idx] = velocities[idx] * dragFactor;
34
- `,
35
- };
36
- }
37
- writeUniforms(view, offset) {
38
- view.setFloat32(offset + 0, this.drag, true);
39
- }
40
- }
39
+ `
40
+ };
41
+ }
42
+ writeUniforms(view, offset) {
43
+ view.setFloat32(offset + 0, this.drag, true);
44
+ }
45
+ };
41
46
 
47
+ //#endregion
42
48
  export { Drag };
43
- //# sourceMappingURL=Drag.js.map
49
+ //# sourceMappingURL=Drag.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"Drag.js","sources":["../../../../src/modules/Drag.ts"],"sourcesContent":[null],"names":[],"mappings":";;AAKA;;;;;;;;;AASG;AACG,MAAO,IAAK,SAAQ,YAAY,CAAA;AAC7B,IAAA,IAAI;AAEX,IAAA,WAAA,CAAmB,IAAY,EAAA;AAC7B,QAAA,KAAK,EAAE;AACP,QAAA,IAAI,CAAC,IAAI,GAAG,IAAI;IAClB;IAEgB,KAAK,CAAC,MAAsB,EAAE,EAAU,EAAA;QACtD,MAAM,EAAE,IAAI,EAAE,IAAI,EAAE,SAAS,EAAE,GAAG,MAAM;QACxC,MAAM,MAAM,GAAG,CAAC,GAAG,IAAI,CAAC,IAAI,GAAG,EAAE;AAEjC,QAAA,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,SAAS,EAAE,CAAC,EAAE,EAAE;AAClC,YAAA,IAAI,CAAC,CAAC,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,MAAM;AACjC,YAAA,IAAI,CAAC,CAAC,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,MAAM;QACnC;IACF;IAEgB,IAAI,GAAA;QAClB,OAAO;AACL,YAAA,GAAG,EAAE,MAAM;YACX,QAAQ,EAAE,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC;AACzC,YAAA,IAAI,EAAE;;;AAGC,YAAA,CAAA;SACR;IACH;IAEgB,aAAa,CAAC,IAAc,EAAE,MAAc,EAAA;AAC1D,QAAA,IAAI,CAAC,UAAU,CAAC,MAAM,GAAG,CAAC,EAAE,IAAI,CAAC,IAAI,EAAE,IAAI,CAAC;IAC9C;AACD;;;;"}
1
+ {"version":3,"file":"Drag.js","names":[],"sources":["../../../src/modules/Drag.ts"],"sourcesContent":["import type { ParticleBatch } from '#ParticleStorage';\n\nimport { UpdateModule } from './UpdateModule';\nimport type { WgslContribution } from './WgslContribution';\n\n/**\n * Exponential velocity damping. Each frame multiplies every live particle's\n * velocity by `(1 - drag * dt)`, simulating linear air resistance.\n *\n * `drag = 0` is no damping; `drag = 1` halves velocity in ~1 second; higher\n * values slow particles faster. Negative values accelerate (don't do that\n * unless you mean it).\n *\n * GPU-eligible.\n */\nexport class Drag extends UpdateModule {\n public drag: number;\n\n public constructor(drag: number) {\n super();\n this.drag = drag;\n }\n\n public override apply(particles: ParticleBatch, dt: number): void {\n const { x: velX, y: velY } = particles.velocity;\n const liveCount = particles.count;\n const factor = 1 - this.drag * dt;\n\n for (let i = 0; i < liveCount; i++) {\n velX[i] = (velX[i] ?? 0) * factor;\n velY[i] = (velY[i] ?? 0) * factor;\n }\n }\n\n public override wgsl(): WgslContribution {\n return {\n key: 'Drag',\n uniforms: [{ name: 'drag', type: 'f32' }],\n body: `\n let dragFactor = 1.0 - modules.u_Drag.drag * dt;\n velocities[idx] = velocities[idx] * dragFactor;\n `,\n };\n }\n\n public override writeUniforms(view: DataView, offset: number): void {\n view.setFloat32(offset + 0, this.drag, true);\n }\n}\n"],"mappings":";;;;;;;;;;;;;AAeA,IAAa,OAAb,cAA0B,aAAa;CACrC,AAAO;CAEP,AAAO,YAAY,MAAc;EAC/B,MAAM;EACN,KAAK,OAAO;CACd;CAEA,AAAgB,MAAM,WAA0B,IAAkB;EAChE,MAAM,EAAE,GAAG,MAAM,GAAG,SAAS,UAAU;EACvC,MAAM,YAAY,UAAU;EAC5B,MAAM,SAAS,IAAI,KAAK,OAAO;EAE/B,KAAK,IAAI,IAAI,GAAG,IAAI,WAAW,KAAK;GAClC,KAAK,MAAM,KAAK,MAAM,KAAK;GAC3B,KAAK,MAAM,KAAK,MAAM,KAAK;EAC7B;CACF;CAEA,AAAgB,OAAyB;EACvC,OAAO;GACL,KAAK;GACL,UAAU,CAAC;IAAE,MAAM;IAAQ,MAAM;GAAM,CAAC;GACxC,MAAM;;;;EAIR;CACF;CAEA,AAAgB,cAAc,MAAgB,QAAsB;EAClE,KAAK,WAAW,SAAS,GAAG,KAAK,MAAM,IAAI;CAC7C;AACF"}
@@ -1,8 +1,8 @@
1
- import type { ParticleSystem } from "#ParticleSystem";
1
+ import type { ParticleBatch } from '#ParticleStorage';
2
2
  import { UpdateModule } from './UpdateModule';
3
3
  import type { WgslContribution } from './WgslContribution';
4
4
  /**
5
- * Applies a tangential acceleration around a center point perpendicular
5
+ * Applies a tangential acceleration around a center point - perpendicular
6
6
  * to the radial vector `(particle − center)`. Combined with an attract /
7
7
  * repel module that controls the radial distance, this produces orbital,
8
8
  * spiral, or vortex motion.
@@ -22,7 +22,8 @@ export declare class OrbitalForce extends UpdateModule {
22
22
  y: number;
23
23
  angularSpeed: number;
24
24
  constructor(x: number, y: number, angularSpeed: number);
25
- apply(system: ParticleSystem, dt: number): void;
25
+ apply(particles: ParticleBatch, dt: number): void;
26
26
  wgsl(): WgslContribution;
27
27
  writeUniforms(view: DataView, offset: number): void;
28
28
  }
29
+ //# sourceMappingURL=OrbitalForce.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"OrbitalForce.d.ts","sourceRoot":"","sources":["../../../src/modules/OrbitalForce.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,kBAAkB,CAAC;AAEtD,OAAO,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAC;AAC9C,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,oBAAoB,CAAC;AAE3D;;;;;;;;;;;;;;;GAeG;AACH,qBAAa,YAAa,SAAQ,YAAY;IACrC,CAAC,EAAE,MAAM,CAAC;IACV,CAAC,EAAE,MAAM,CAAC;IACV,YAAY,EAAE,MAAM,CAAC;gBAET,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,EAAE,YAAY,EAAE,MAAM;IAO7C,KAAK,CAAC,SAAS,EAAE,aAAa,EAAE,EAAE,EAAE,MAAM,GAAG,IAAI;IAgBjD,IAAI,IAAI,gBAAgB;IAgBxB,aAAa,CAAC,IAAI,EAAE,QAAQ,EAAE,MAAM,EAAE,MAAM,GAAG,IAAI;CAMpE"}