@rzmps/rzmps 1.0.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 (133) hide show
  1. package/README.md +1048 -0
  2. package/build/EmissionShape.d.ts +33 -0
  3. package/build/EmissionShape.js +129 -0
  4. package/build/Emitter.d.ts +58 -0
  5. package/build/Emitter.js +166 -0
  6. package/build/GpuModule.d.ts +28 -0
  7. package/build/GpuModule.js +28 -0
  8. package/build/Module.d.ts +19 -0
  9. package/build/Module.js +31 -0
  10. package/build/Particle.d.ts +60 -0
  11. package/build/Particle.js +52 -0
  12. package/build/ParticleForceField.d.ts +39 -0
  13. package/build/ParticleForceField.js +101 -0
  14. package/build/ParticleForceFieldHelper.d.ts +18 -0
  15. package/build/ParticleForceFieldHelper.js +103 -0
  16. package/build/ParticleSystem.d.ts +122 -0
  17. package/build/ParticleSystem.js +609 -0
  18. package/build/Renderer.d.ts +15 -0
  19. package/build/Renderer.js +11 -0
  20. package/build/assets/images/default.png +0 -0
  21. package/build/collision/ThreeCollisionBackend.d.ts +47 -0
  22. package/build/collision/ThreeCollisionBackend.js +340 -0
  23. package/build/enums/Axis.d.ts +6 -0
  24. package/build/enums/Axis.js +7 -0
  25. package/build/enums/DynamicValueType.d.ts +6 -0
  26. package/build/enums/DynamicValueType.js +7 -0
  27. package/build/enums/EmissionSource copy.d.ts +7 -0
  28. package/build/enums/EmissionSource copy.js +7 -0
  29. package/build/enums/EmissionSource.d.ts +7 -0
  30. package/build/enums/EmissionSource.js +7 -0
  31. package/build/enums/EndBehavior.d.ts +6 -0
  32. package/build/enums/EndBehavior.js +7 -0
  33. package/build/enums/SimulationSpace.d.ts +5 -0
  34. package/build/enums/SimulationSpace.js +6 -0
  35. package/build/enums/SpriteMaterialType.d.ts +5 -0
  36. package/build/enums/SpriteMaterialType.js +6 -0
  37. package/build/enums/TagSelectionMethod.d.ts +6 -0
  38. package/build/enums/TagSelectionMethod.js +7 -0
  39. package/build/enums/TrailMode copy.d.ts +5 -0
  40. package/build/enums/TrailMode copy.js +7 -0
  41. package/build/enums/TrailMode.d.ts +5 -0
  42. package/build/enums/TrailMode.js +7 -0
  43. package/build/enums/TrailTextureMode.d.ts +7 -0
  44. package/build/enums/TrailTextureMode.js +9 -0
  45. package/build/gpu/GpuDynamicValue.d.ts +9 -0
  46. package/build/gpu/GpuDynamicValue.js +113 -0
  47. package/build/gpu/GpuMath.d.ts +3 -0
  48. package/build/gpu/GpuMath.js +12 -0
  49. package/build/gpu/GpuParticleContext.d.ts +63 -0
  50. package/build/gpu/GpuParticleContext.js +291 -0
  51. package/build/helpers/acceptMultiple.d.ts +2 -0
  52. package/build/helpers/acceptMultiple.js +6 -0
  53. package/build/helpers/evaluateDynamic.d.ts +2 -0
  54. package/build/helpers/evaluateDynamic.js +18 -0
  55. package/build/helpers/evaluateDynamicColor.d.ts +3 -0
  56. package/build/helpers/evaluateDynamicColor.js +5 -0
  57. package/build/helpers/evaluateDynamicNumber.d.ts +2 -0
  58. package/build/helpers/evaluateDynamicNumber.js +4 -0
  59. package/build/helpers/evaluateDynamicVector3.d.ts +3 -0
  60. package/build/helpers/evaluateDynamicVector3.js +5 -0
  61. package/build/helpers/isPointInMesh.d.ts +2 -0
  62. package/build/helpers/isPointInMesh.js +8 -0
  63. package/build/helpers/particleRatio.d.ts +2 -0
  64. package/build/helpers/particleRatio.js +13 -0
  65. package/build/helpers/tagsIntersect.d.ts +2 -0
  66. package/build/helpers/tagsIntersect.js +4 -0
  67. package/build/index.d.ts +37 -0
  68. package/build/index.js +34 -0
  69. package/build/interfaces/ICollisionBackend.d.ts +21 -0
  70. package/build/interfaces/ICollisionBackend.js +1 -0
  71. package/build/interfaces/IParticleForceField.d.ts +5 -0
  72. package/build/interfaces/IParticleForceField.js +1 -0
  73. package/build/interfaces/InitialParticleValues.d.ts +18 -0
  74. package/build/interfaces/InitialParticleValues.js +1 -0
  75. package/build/materials/BasicSprite.d.ts +21 -0
  76. package/build/materials/BasicSprite.js +46 -0
  77. package/build/materials/UnlitSprite.d.ts +13 -0
  78. package/build/materials/UnlitSprite.js +31 -0
  79. package/build/materials/WebGPUBasicSprite.d.ts +8 -0
  80. package/build/materials/WebGPUBasicSprite.js +65 -0
  81. package/build/materials/WebGPUUnlitSprite.d.ts +8 -0
  82. package/build/materials/WebGPUUnlitSprite.js +49 -0
  83. package/build/modules/Audio.d.ts +60 -0
  84. package/build/modules/Audio.js +181 -0
  85. package/build/modules/Collision.d.ts +40 -0
  86. package/build/modules/Collision.js +162 -0
  87. package/build/modules/ColorBySpeed.d.ts +18 -0
  88. package/build/modules/ColorBySpeed.js +26 -0
  89. package/build/modules/ColorOverLifetime.d.ts +12 -0
  90. package/build/modules/ColorOverLifetime.js +17 -0
  91. package/build/modules/ExternalForces.d.ts +21 -0
  92. package/build/modules/ExternalForces.js +61 -0
  93. package/build/modules/ForceOverLifetime.d.ts +11 -0
  94. package/build/modules/ForceOverLifetime.js +12 -0
  95. package/build/modules/LimitVelocityOverLifetime.d.ts +16 -0
  96. package/build/modules/LimitVelocityOverLifetime.js +34 -0
  97. package/build/modules/MassOverLifetime.d.ts +11 -0
  98. package/build/modules/MassOverLifetime.js +15 -0
  99. package/build/modules/NoiseModule.d.ts +23 -0
  100. package/build/modules/NoiseModule.js +45 -0
  101. package/build/modules/RotationBySpeed.d.ts +14 -0
  102. package/build/modules/RotationBySpeed.js +19 -0
  103. package/build/modules/RotationOverLifetime.d.ts +11 -0
  104. package/build/modules/RotationOverLifetime.js +12 -0
  105. package/build/modules/ScaleBySpeed.d.ts +14 -0
  106. package/build/modules/ScaleBySpeed.js +19 -0
  107. package/build/modules/ScaleOverLifetime.d.ts +11 -0
  108. package/build/modules/ScaleOverLifetime.js +11 -0
  109. package/build/modules/TransformByNoise.d.ts +17 -0
  110. package/build/modules/TransformByNoise.js +32 -0
  111. package/build/modules/VelocityOverLifetime.d.ts +15 -0
  112. package/build/modules/VelocityOverLifetime.js +32 -0
  113. package/build/renderers/LightRenderer.d.ts +54 -0
  114. package/build/renderers/LightRenderer.js +141 -0
  115. package/build/renderers/MeshRenderer.d.ts +27 -0
  116. package/build/renderers/MeshRenderer.js +70 -0
  117. package/build/renderers/SpriteRenderer.d.ts +95 -0
  118. package/build/renderers/SpriteRenderer.js +456 -0
  119. package/build/renderers/TrailRenderer.d.ts +71 -0
  120. package/build/renderers/TrailRenderer.js +400 -0
  121. package/build/shaders/BasicSprite.frag.js +1 -0
  122. package/build/shaders/BasicSprite.vert.js +1 -0
  123. package/build/shaders/UnlitSprite.frag.js +1 -0
  124. package/build/shaders/UnlitSprite.vert.js +1 -0
  125. package/build/types/DynamicValue.d.ts +2 -0
  126. package/build/types/DynamicValue.js +1 -0
  127. package/build/types/EnumStringLiteral.d.ts +1 -0
  128. package/build/types/EnumStringLiteral.js +1 -0
  129. package/build/types/Multiple.d.ts +2 -0
  130. package/build/types/Multiple.js +1 -0
  131. package/build/types/Tag.d.ts +1 -0
  132. package/build/types/Tag.js +1 -0
  133. package/package.json +39 -0
package/README.md ADDED
@@ -0,0 +1,1048 @@
1
+ # RZMPS
2
+
3
+ **🧅 Robert May Particle System** is a modular, extensible particle system for
4
+ [Three.js](https://threejs.org/).
5
+
6
+ [![npm](https://img.shields.io/npm/v/rzmps)](https://www.npmjs.com/package/rzmps)
7
+ [![license](https://img.shields.io/npm/l/rzmps)](https://github.com/rzmay/rzmps)
8
+
9
+ | Resource | Link |
10
+ | ----------- | ------------------------------------------------------------ |
11
+ | npm package | [npmjs.com/package/rzmps](https://www.npmjs.com/package/rzmps) |
12
+ | GitHub repo | [github.com/rzmay/rzmps](https://github.com/rzmay/rzmps) |
13
+ | Live demo | [rzmps.rzmay.com](https://rzmps.rzmay.com) |
14
+
15
+ RZMPS is built from small composable pieces:
16
+
17
+ - **Emitters** create particles from shapes.
18
+ - **Modules** modify particles over time.
19
+ - **Renderers** decide how particles appear in a Three.js scene.
20
+ - **Subsystems** let particles emit other particle systems.
21
+ - **Physics backends** let particles collide with Three.js objects or external
22
+ physics engines.
23
+
24
+ ## Contents
25
+
26
+ - [Installation](#installation)
27
+ - [Quick Start](#quick-start)
28
+ - [Core Types](#core-types)
29
+ - [Particle Systems](#particle-systems)
30
+ - [Emitters](#emitters)
31
+ - [Built-In Modules](#built-in-modules)
32
+ - [Built-In Renderers](#built-in-renderers)
33
+ - [Force Fields](#force-fields)
34
+ - [Collision And Physics](#collision-and-physics)
35
+ - [Custom Modules](#custom-modules)
36
+ - [Developer Guide](#developer-guide)
37
+
38
+ ## Installation
39
+
40
+ ```bash
41
+ npm install rzmps three
42
+ ```
43
+
44
+ `three` is a peer dependency.
45
+
46
+ ## Quick Start
47
+
48
+ ```ts
49
+ import * as THREE from "three";
50
+ import {
51
+ ColorOverLifetime,
52
+ EmissionShape,
53
+ Emitter,
54
+ ParticleSystem,
55
+ ScaleOverLifetime,
56
+ SpriteRenderer,
57
+ } from "rzmps";
58
+
59
+ const particles = new ParticleSystem({
60
+ emitters: new Emitter({
61
+ source: EmissionShape.Sphere(),
62
+ rate: 80,
63
+ radialSpeed: 2,
64
+ initialValues: {
65
+ lifetime: 1.5,
66
+ scale: new THREE.Vector3(0.25, 0.25, 0.25),
67
+ color: new THREE.Color("#ff9f43"),
68
+ },
69
+ }),
70
+ modules: [
71
+ new ColorOverLifetime({
72
+ alpha: (t) => 1 - t,
73
+ }),
74
+ new ScaleOverLifetime({
75
+ scale: (t) => new THREE.Vector3(1 + t, 1 + t, 1 + t),
76
+ }),
77
+ ],
78
+ renderers: new SpriteRenderer(undefined, {
79
+ materialOptions: {
80
+ blending: THREE.AdditiveBlending,
81
+ depthWrite: false,
82
+ },
83
+ }),
84
+ });
85
+
86
+ scene.add(particles);
87
+
88
+ function animate() {
89
+ requestAnimationFrame(animate);
90
+ particles.update();
91
+ renderer.render(scene, camera);
92
+ }
93
+
94
+ animate();
95
+ ```
96
+
97
+ ## Core Types
98
+
99
+ ### Dynamic Values
100
+
101
+ Many RZMPS options use `DynamicValue<T>`.
102
+
103
+ ```ts
104
+ type DynamicValue<T> =
105
+ | T
106
+ | ((t: number) => DynamicValue<T>)
107
+ | [DynamicValue<T>, DynamicValue<T>]
108
+ | Set<DynamicValue<T>>;
109
+
110
+ // Used for parameters that can take multiple elements or just one
111
+ type Multiple<T> = T | T[];
112
+
113
+ // Multiple with least one value
114
+ type StrictMultiple<T> = T | [T, ...[T]];
115
+
116
+ // In RZMPS, tags are natively strings
117
+ type Tag = string;
118
+ ```
119
+
120
+ The `t` argument is normalized particle lifetime, from `0` to `1`.
121
+
122
+ | Form | Example | Behavior |
123
+ | -------------- | ----------------------------- | -------------------------------------- |
124
+ | Static value | `2` | Always returns the same value. |
125
+ | Function | `(t) => 1 - t` | Re-evaluates over normalized lifetime. |
126
+ | Two-item array | `[0.5, 2]` | Random value between min and max. |
127
+ | Set | `new Set(["spark", "smoke"])` | Random choice. |
128
+
129
+ Dynamic values are supported for numbers, vectors, and colors where the option
130
+ type uses `DynamicValue<number>`, `DynamicValue<THREE.Vector3>`, or
131
+ `DynamicValue<THREE.Color>`.
132
+
133
+ The [curves](https://www.npmjs.com/package/curves) package was designed and
134
+ built to streamline functional values by defining and evaluating eased curves,
135
+ similar to Unity's AnimationCurves with modifiers akin to Blender's FCurve
136
+ Modifiers. It has prebuilt keyframe types for numbers, colors, and vectors,
137
+ compatible with the `THREE.Vector3` and `THREE.Color` types.
138
+
139
+ ### Tags
140
+
141
+ Emitters can assign tags to particles. Modules and renderers can filter by tags.
142
+
143
+ ```ts
144
+ const emitter = new Emitter({
145
+ tags: ["spark", "hot"],
146
+ tagSelection: "all",
147
+ });
148
+
149
+ const sparksOnly = new SpriteRenderer(undefined, { tags: "spark" });
150
+ const fadeHot = new ColorOverLifetime({ tags: "hot", alpha: (t) => 1 - t });
151
+ ```
152
+
153
+ `tagSelection` can follows the `TagSelectionMethod` enum, and can be:
154
+
155
+ ```ts
156
+ enum TagSelectionMethod {
157
+ All = "all",
158
+ Random = "random",
159
+ Distribute = "distribute",
160
+ }
161
+ ```
162
+
163
+ | Value | Behavior |
164
+ | ------------ | -------------------------------------------------------- |
165
+ | `All` | Every emitted particle receives every emitter tag.scene. |
166
+ | `Random` | Each particle receives one random tag. |
167
+ | `Distribute` | Tags are assigned round-robin. |
168
+
169
+ ## Particle Systems
170
+
171
+ `ParticleSystem` extends `THREE.Object3D`, so it can be added, moved, rotated,
172
+ and scaled like any other Three.js object.
173
+
174
+ ```ts
175
+ new ParticleSystem(options?: Partial<ParticleSystemOptions>)
176
+ ```
177
+
178
+ ```ts
179
+ interface ParticleSystemOptions {
180
+ emitters: Multiple<Emitter>;
181
+ renderers: Multiple<Renderer>;
182
+ modules: Multiple<Module>;
183
+ duration: number;
184
+ looping: boolean;
185
+ endBehavior: EndBehavior;
186
+ gravity: THREE.Vector3;
187
+ gravityModifier: DynamicValue<number>;
188
+ simulationSpace: SimulationSpace;
189
+ }
190
+ ```
191
+
192
+ `duration` and `looping` control the particle system's emission timeline.
193
+ Emitters use normalized system time for rate curves and bursts.
194
+
195
+ For non-looping systems, `endBehavior` controls what happens after the system
196
+ duration has elapsed.
197
+
198
+ ```ts
199
+ enum EndBehavior {
200
+ None = "none",
201
+ Destroy = "destroy",
202
+ DestroyImmediate = "destroyImmediate",
203
+ }
204
+ ```
205
+
206
+ | Value | Behavior |
207
+ | ------------------ | --------------------------------------------------------------------- |
208
+ | `None` | Stops emission and keeps the system in the scene. |
209
+ | `Destroy` | Stops emission, lets live particles finish, then destroys the system. |
210
+ | `DestroyImmediate` | Destroys the system as soon as its duration elapses. |
211
+
212
+ ### Simulation Space
213
+
214
+ `simulationSpace` follows the `SimulationSpace` enum and controls whether
215
+ particle positions are stored relative to the particle system or in world space.
216
+
217
+ ```ts
218
+ enum EndBehavior {
219
+ Local = "local",
220
+ World = "world",
221
+ }
222
+ ```
223
+
224
+ | Value | Behavior |
225
+ | ------- | ---------------------------------------------------------------------------------------------- |
226
+ | `Local` | Particles move with the particle system transform. |
227
+ | `World` | New particles spawn from the system transform, then remain in world space if the system moves. |
228
+
229
+ Subsystems inherit the parent system's simulation space.
230
+
231
+ ### Control Methods
232
+
233
+ | Method | Description |
234
+ | ------------------------------- | ------------------------------------------------------------ |
235
+ | `update()` | Advances the particle system. Call once per animation frame. |
236
+ | `start()` | Starts or restarts emission. |
237
+ | `pause()` | Pauses emission and simulation. |
238
+ | `resume()` | Resumes from pause. |
239
+ | `stop(clearParticles)` | Stops emission; optionally clears existing particles. |
240
+ | `destroy()` | Stops, clears, destroys renderers, and removes the system. |
241
+ | `clearParticles()` | Removes all live particles. |
242
+ | `addEmitter(emitter)` | Adds and sets up an emitter. |
243
+ | `addModule(module)` | Adds a module. |
244
+ | `addRenderer(renderer)` | Adds and sets up a renderer. |
245
+ | `addSubSystem(system, options)` | Adds a child particle system emitted by particles. |
246
+
247
+ ### Event Listeners
248
+
249
+ `ParticleSystem` exposes listener helpers for particle lifecycle events.
250
+
251
+ ```ts
252
+ type ParticleListener = (particle: Particle) => void;
253
+ type CollisionListener = (particle: Particle, collision: CollisionHit) => void;
254
+
255
+ system.onSpawn((particle) => {
256
+ console.log("spawned", particle.id);
257
+ });
258
+
259
+ system.onDeath((particle) => {
260
+ console.log("died", particle.id);
261
+ });
262
+
263
+ system.onCollision((particle, collision) => {
264
+ console.log("hit", particle.id, collision);
265
+ });
266
+ ```
267
+
268
+ | Method | Description |
269
+ | ----------------------------------- | ----------------------------------------------------- |
270
+ | `onSpawn(listener)` | Runs when a particle is emitted. |
271
+ | `removeSpawnListener(listener)` | Removes a spawn listener. |
272
+ | `onDeath(listener)` | Runs when a particle reaches the end of its lifetime. |
273
+ | `removeDeathListener(listener)` | Removes a death listener. |
274
+ | `onCollision(listener)` | Runs when the `Collision` module reports a hit. |
275
+ | `removeCollisionListener(listener)` | Removes a collision listener. |
276
+
277
+ ### Subsystems
278
+
279
+ ```ts
280
+ interface SubSystemOptions {
281
+ shouldEmit: boolean | ((particle: Particle) => boolean);
282
+ ratio: number;
283
+ emitContinuous: boolean;
284
+ emitOnCollision: boolean;
285
+ emitOnSpawn: boolean;
286
+ emitOnDeath: boolean;
287
+ inheritScale: boolean;
288
+ inheritLifetime: boolean;
289
+ inheritColor: boolean;
290
+ inheritAlpha: boolean;
291
+ inheritMass: boolean;
292
+ }
293
+ ```
294
+
295
+ ```ts
296
+ rocketSystem.addSubSystem(sparkSystem, {
297
+ emitOnDeath: true,
298
+ emitContinuous: false,
299
+ inheritColor: true,
300
+ inheritAlpha: true,
301
+ inheritScale: true,
302
+ });
303
+ ```
304
+
305
+ Subsystems are owned and updated by their parent. They can emit continuously
306
+ from parent particles, or trigger emission runs on spawn, collision, or death.
307
+
308
+ ## Emitters
309
+
310
+ ```ts
311
+ new Emitter(options?: Partial<EmitterOptions>)
312
+ ```
313
+
314
+ ```ts
315
+ type SpawnBurst = {
316
+ time: number;
317
+ count: DynamicValue<number>;
318
+ };
319
+
320
+ interface EmitterOptions {
321
+ initialValues: Partial<InitialParticleValues>;
322
+ source: EmissionShape;
323
+ bursts: SpawnBurst | SpawnBurst[];
324
+ rate: DynamicValue<number>;
325
+ radialSpeed: DynamicValue<number>;
326
+ alignment: DynamicValue<number>;
327
+ tags: StrictMultiple<Tag>;
328
+ tagSelection: TagSelectionMethod;
329
+ }
330
+ ```
331
+
332
+ Emitter `rate` curves and burst `time` values are evaluated against the owning
333
+ particle system's normalized timeline.
334
+
335
+ ### Initial Particle Values
336
+
337
+ Common `initialValues` fields include:
338
+
339
+ | Field | Type | Description |
340
+ | --------------------- | --------------- | ------------------------------------------------------------------ |
341
+ | `position` | `THREE.Vector3` | Initial particle position. Usually supplied by the emission shape. |
342
+ | `rotation` | `THREE.Vector3` | Initial Euler rotation. |
343
+ | `scale` | `THREE.Vector3` | Initial particle scale. |
344
+ | `velocity` | `THREE.Vector3` | Initial linear velocity. |
345
+ | `angularVelocity` | `THREE.Vector3` | Initial rotational velocity. |
346
+ | `scalarVelocity` | `THREE.Vector3` | Initial scale velocity. |
347
+ | `acceleration` | `THREE.Vector3` | Initial linear acceleration. |
348
+ | `angularAcceleration` | `THREE.Vector3` | Initial rotational acceleration. |
349
+ | `scalarAcceleration` | `THREE.Vector3` | Initial scale acceleration. |
350
+ | `lifetime` | `number` | Lifetime in seconds. |
351
+ | `speed` | `number` | Multiplier applied to particle simulation speed. |
352
+ | `color` | `THREE.Color` | Initial particle color. |
353
+ | `alpha` | `number` | Initial opacity. |
354
+ | `mass` | `number` | Particle mass for collision impulses. Defaults to `0`. |
355
+
356
+ Most initial values can be dynamic.
357
+
358
+ ### Emission Shapes
359
+
360
+ ```ts
361
+ new EmissionShape(options?: Partial<EmissionShapeOptions>)
362
+ ```
363
+
364
+ ```ts
365
+ interface EmissionShapeOptions {
366
+ geometry: THREE.BufferGeometry;
367
+ source: EmissionSource;
368
+ }
369
+
370
+ enum EmissionSource {
371
+ Volume = "volume",
372
+ Surface = "surface",
373
+ Vertices = "vertices",
374
+ }
375
+ ```
376
+
377
+ Helpers:
378
+
379
+ ```ts
380
+ EmissionShape.Box(...boxGeometryArgs);
381
+ EmissionShape.Sphere(...sphereGeometryArgs);
382
+ EmissionShape.Cone(...coneGeometryArgs);
383
+ EmissionShape.Torus(...torusGeometryArgs);
384
+ ```
385
+
386
+ ## Built-In Modules
387
+
388
+ All built-in modules extend `Module`. Constructor option objects are partials,
389
+ so you only need to pass the fields you want to customize.
390
+
391
+ ```ts
392
+ interface ModuleOptions {
393
+ priority: number;
394
+ tags: StrictMultiple<Tag>;
395
+ }
396
+ ```
397
+
398
+ | Option | Description |
399
+ | ---------- | -------------------------------------------------------------------------------------------------------------------------------- |
400
+ | `priority` | Modules with priority below `0` run before movement. Modules with priority `0` or higher run after movement, sorted by priority. |
401
+ | `tags` | Restricts the module to particles with matching tags. |
402
+
403
+ ### VelocityOverLifetime
404
+
405
+ ```ts
406
+ new VelocityOverLifetime(options?: Partial<VelocityOverLifetimeOptions>)
407
+
408
+ interface VelocityOverLifetimeOptions extends Partial<ModuleOptions> {
409
+ linear: DynamicValue<THREE.Vector3>;
410
+ orbital: DynamicValue<THREE.Vector3>;
411
+ orbitOffset: DynamicValue<THREE.Vector3>;
412
+ radial: DynamicValue<number>;
413
+ speedModifier: DynamicValue<number>;
414
+ }
415
+ ```
416
+
417
+ Sets particle velocity from the start velocity plus optional linear, orbital,
418
+ and radial terms. `speedModifier` scales particle simulation speed.
419
+
420
+ ### ForceOverLifetime
421
+
422
+ ```ts
423
+ new ForceOverLifetime(options: ForceOverLifetimeOptions)
424
+
425
+ interface ForceOverLifetimeOptions extends Partial<ModuleOptions> {
426
+ force: DynamicValue<THREE.Vector3>;
427
+ }
428
+ ```
429
+
430
+ Adds a dynamic force to particle acceleration.
431
+
432
+ ### LimitVelocityOverLifetime
433
+
434
+ ```ts
435
+ new LimitVelocityOverLifetime(options: LimitVelocityOverLifetimeOptions)
436
+
437
+ interface LimitVelocityOverLifetimeOptions extends Partial<ModuleOptions> {
438
+ limit: DynamicValue<THREE.Vector3>;
439
+ dampen?: number;
440
+ drag?: DynamicValue<number>;
441
+ multiplyDragBySize?: boolean;
442
+ multiplyDragByVelocity?: boolean;
443
+ }
444
+ ```
445
+
446
+ Clamps velocity per axis and optionally applies drag.
447
+
448
+ ### MassOverLifetime
449
+
450
+ ```ts
451
+ new MassOverLifetime(options: MassOverLifetimeOptions)
452
+
453
+ interface MassOverLifetimeOptions extends Partial<ModuleOptions> {
454
+ mass: DynamicValue<number>;
455
+ multiplyMassBySize: boolean;
456
+ }
457
+ ```
458
+
459
+ Changes particle mass over lifetime. `multiplyMassBySize` defaults to `true`.
460
+
461
+ ### ColorOverLifetime
462
+
463
+ ```ts
464
+ new ColorOverLifetime(options?: ColorOverLifetimeOptions)
465
+
466
+ interface ColorOverLifetimeOptions extends Partial<ModuleOptions> {
467
+ color?: DynamicValue<THREE.Color>;
468
+ alpha?: DynamicValue<number>;
469
+ }
470
+ ```
471
+
472
+ Multiplies each particle's start color and alpha over lifetime.
473
+
474
+ ### ColorBySpeed
475
+
476
+ ```ts
477
+ new ColorBySpeed(options: ColorBySpeedOptions)
478
+
479
+ type SpeedRange = [number, number] | { min: number; max: number };
480
+
481
+ interface ColorBySpeedOptions extends Partial<ModuleOptions> {
482
+ color?: DynamicValue<THREE.Color>;
483
+ alpha?: DynamicValue<number>;
484
+ speedRange?: SpeedRange;
485
+ }
486
+ ```
487
+
488
+ Multiplies start color and alpha based on normalized speed within `speedRange`.
489
+
490
+ ### ScaleOverLifetime
491
+
492
+ ```ts
493
+ new ScaleOverLifetime(options: ScaleOverLifetimeOptions)
494
+
495
+ interface ScaleOverLifetimeOptions extends Partial<ModuleOptions> {
496
+ scale: DynamicValue<THREE.Vector3>;
497
+ }
498
+ ```
499
+
500
+ Multiplies each particle's start scale over lifetime.
501
+
502
+ ### ScaleBySpeed
503
+
504
+ ```ts
505
+ new ScaleBySpeed(options: ScaleBySpeedOptions)
506
+
507
+ interface ScaleBySpeedOptions extends Partial<ModuleOptions> {
508
+ scale: DynamicValue<THREE.Vector3>;
509
+ speedRange?: SpeedRange;
510
+ }
511
+ ```
512
+
513
+ Multiplies start scale based on normalized speed within `speedRange`.
514
+
515
+ ### RotationOverLifetime
516
+
517
+ ```ts
518
+ new RotationOverLifetime(options: RotationOverLifetimeOptions)
519
+
520
+ interface RotationOverLifetimeOptions extends Partial<ModuleOptions> {
521
+ angularVelocity: DynamicValue<THREE.Vector3>;
522
+ }
523
+ ```
524
+
525
+ Adjusts angular velocity over lifetime.
526
+
527
+ ### RotationBySpeed
528
+
529
+ ```ts
530
+ new RotationBySpeed(options: RotationBySpeedOptions)
531
+
532
+ interface RotationBySpeedOptions extends Partial<ModuleOptions> {
533
+ angularVelocity: DynamicValue<THREE.Vector3>;
534
+ speedRange?: SpeedRange;
535
+ }
536
+ ```
537
+
538
+ Adjusts angular velocity based on normalized speed within `speedRange`.
539
+
540
+ ### NoiseModule
541
+
542
+ ```ts
543
+ new NoiseModule(key: string, options?: Partial<NoiseOptions>)
544
+
545
+ interface NoiseOptions extends Partial<ModuleOptions> {
546
+ octaves: number;
547
+ frequency: number;
548
+ lacunarity: number;
549
+ persistence: number;
550
+ time: number;
551
+ offset: THREE.Vector3;
552
+ }
553
+ ```
554
+
555
+ Writes simplex noise into `particle.noise[key]` as `{ noise, noise4d }`.
556
+
557
+ ### TransformByNoise
558
+
559
+ ```ts
560
+ new TransformByNoise(options: Partial<TransformByNoiseOptions>)
561
+
562
+ interface TransformByNoiseOptions extends Partial<ModuleOptions>, NoiseOptions {
563
+ strength: DynamicValue<THREE.Vector3>;
564
+ scrollSpeed: DynamicValue<number>;
565
+ damping: boolean;
566
+ }
567
+ ```
568
+
569
+ Pushes particles through animated 3D noise. Internally, it creates dependent
570
+ `NoiseModule` instances for the X, Y, and Z axes.
571
+
572
+ ### ExternalForces
573
+
574
+ ```ts
575
+ new ExternalForces(options: ExternalForcesOptions)
576
+
577
+ interface ExternalForcesOptions extends Partial<ModuleOptions> {
578
+ multiplier?: DynamicValue<number>;
579
+ forceFieldFilter?: (forceField: ParticleForceField) => boolean;
580
+ forceFields?: IParticleForceField[];
581
+ }
582
+ ```
583
+
584
+ Samples `ParticleForceField` objects and applies their forces to particles. If
585
+ `forceFields` is omitted, fields are discovered from the particle system's
586
+ scene.
587
+
588
+ ### Collision
589
+
590
+ ```ts
591
+ new Collision(options?: Partial<CollisionOptions>)
592
+
593
+ interface CollisionOptions extends Partial<ModuleOptions> {
594
+ backend: ICollisionBackend;
595
+ dampen: DynamicValue<number>;
596
+ bounce: DynamicValue<number>;
597
+ lifetimeLoss: DynamicValue<number>;
598
+ applyImpulses: boolean;
599
+ radiusScale: number;
600
+ minKillSpeed: number;
601
+ maxKillSpeed: number;
602
+ onCollision?: CollisionListener;
603
+ }
604
+ ```
605
+
606
+ Detects collisions using an `ICollisionBackend`, reflects velocity with bounce
607
+ and dampening, reduces lifetime on impact, and can apply impulses to dynamic
608
+ physics bodies when supported by the backend.
609
+
610
+ ### Audio
611
+
612
+ ```ts
613
+ new Audio(options?: Partial<AudioOptions>)
614
+
615
+ interface AudioOptions extends Partial<ModuleOptions> {
616
+ listener: THREE.AudioListener;
617
+ sound: AudioBuffer | AudioBuffer[];
618
+ onCollisionSound: AudioBuffer | AudioBuffer[];
619
+ onSpawnSound: AudioBuffer | AudioBuffer[];
620
+ onDeathSound: AudioBuffer | AudioBuffer[];
621
+ shouldPlay: (particle: Particle) => boolean;
622
+ loop: boolean;
623
+ ratio: number;
624
+ collisionRatio: number;
625
+ pitch: DynamicValue<number>;
626
+ volume: DynamicValue<number>;
627
+ sizeAffectsPitch: number;
628
+ sizeAffectsVolume: number;
629
+ alphaAffectsPitch: number;
630
+ alphaAffectsVolume: number;
631
+ speedAffectsPitch: number;
632
+ speedAffectsVolume: number;
633
+ }
634
+ ```
635
+
636
+ Adds positional audio to particles and can play event sounds for spawn,
637
+ collision, and death.
638
+
639
+ ## Built-In Renderers
640
+
641
+ All renderers accept shared renderer options:
642
+
643
+ ```ts
644
+ interface RendererOptions {
645
+ tags: StrictMultiple<Tag>;
646
+ }
647
+ ```
648
+
649
+ ### SpriteRenderer
650
+
651
+ ```ts
652
+ new SpriteRenderer(
653
+ texture?: string | THREE.Texture,
654
+ options?: Partial<SpriteRendererOptions>
655
+ )
656
+
657
+ interface SpriteRendererOptions extends RendererOptions {
658
+ fps: DynamicValue<number>;
659
+ tileSize: { x: number; y: number };
660
+ tileMargin: { x: number; y: number };
661
+ gridSize: { x: number; y: number };
662
+ frames: number;
663
+ randomStartFrame: boolean;
664
+ alphaMap: string | THREE.Texture;
665
+ material: SpriteMaterialType;
666
+ materialOptions: BasicSpriteOptions | UnlitSpriteOptions;
667
+ castShadow: boolean;
668
+ softParticleDistance: number;
669
+ }
670
+ ```
671
+
672
+ `SpriteRenderer` renders particles as GPU points. It supports sprite sheets,
673
+ alpha maps, random start frames, shadows, and soft particles.
674
+
675
+ `SpriteMaterialType` is an enum consisting of two string values:
676
+
677
+ ```ts
678
+ enum SpriteMaterialType {
679
+ Basic = "basic",
680
+ Unlit = "unlit",
681
+ }
682
+ ```
683
+
684
+ `material: "unlit"` uses `UnlitSpriteOptions`:
685
+
686
+ ```ts
687
+ interface UnlitSpriteOptions {
688
+ gridSize: { x: number; y: number };
689
+ frames: number;
690
+ alphaMap: THREE.Texture;
691
+ softParticles: boolean;
692
+ softParticleDistance: number;
693
+ }
694
+ ```
695
+
696
+ `material: "basic"` uses `BasicSpriteOptions`, which extends
697
+ `THREE.ShaderMaterialParameters`:
698
+
699
+ ```ts
700
+ interface BasicSpriteOptions extends THREE.ShaderMaterialParameters {
701
+ gridSize: { x: number; y: number };
702
+ frames: number;
703
+ alphaMap: THREE.Texture;
704
+ normalMap: THREE.Texture;
705
+ normalStrength: number;
706
+ normalLighting: number;
707
+ sphericalNormals: boolean;
708
+ roughness: number;
709
+ roughnessMap: THREE.Texture;
710
+ envMap: THREE.Texture;
711
+ envIntensity: number;
712
+ softParticles: boolean;
713
+ softParticleDistance: number;
714
+ }
715
+ ```
716
+
717
+ Soft particles read scene depth from the active Three.js renderer and fade near
718
+ intersections. Set `softParticleDistance` above `0` to enable the effect.
719
+
720
+ ### MeshRenderer
721
+
722
+ ```ts
723
+ new MeshRenderer(options?: Partial<MeshRendererOptions>)
724
+
725
+ interface MeshRendererOptions extends RendererOptions {
726
+ mesh: THREE.Mesh;
727
+ maxParticles: number;
728
+ geometry: THREE.BufferGeometry;
729
+ material: THREE.MeshStandardMaterial;
730
+ materialOptions: THREE.MeshStandardMaterialParameters;
731
+ castShadow: boolean;
732
+ receiveShadow: boolean;
733
+ }
734
+ ```
735
+
736
+ Renders particles as an `InstancedMesh`. Pass a complete `mesh`, or pass
737
+ `geometry`, `material`, or `materialOptions`.
738
+
739
+ ### TrailRenderer
740
+
741
+ ```ts
742
+ new TrailRenderer(options?: Partial<TrailRendererOptions>)
743
+
744
+ enum TrailMode {
745
+ Particle = "particle",
746
+ Ribbon = "ribbon",
747
+ }
748
+
749
+ enum TrailTextureMode {
750
+ Stretch = "stretch",
751
+ Tile = "tile",
752
+ RepeatPerSegment = "repeat",
753
+ DistributePerSegment = "distribute",
754
+ }
755
+
756
+ interface TrailRendererOptions extends RendererOptions {
757
+ mode: TrailMode;
758
+ textureMode: TrailTextureMode;
759
+ ratio: number;
760
+ lifetime: DynamicValue<number>;
761
+ minimumVertexDistance: number;
762
+ dieWithParticles: boolean;
763
+ ribbonCount: number;
764
+ width: DynamicValue<number>;
765
+ widthOverTrail: DynamicValue<number>;
766
+ sizeAffectsWidth: boolean;
767
+ sizeAffectsLifetime: boolean;
768
+ inheritParticleColor: boolean;
769
+ colorOverLifetime: DynamicValue<THREE.Color>;
770
+ colorOverTrail: DynamicValue<THREE.Color>;
771
+ material: THREE.Material;
772
+ materialOptions: THREE.MeshStandardMaterialParameters;
773
+ castShadow: boolean;
774
+ receiveShadow: boolean;
775
+ }
776
+ ```
777
+
778
+ Builds per-particle trails or ribbon trails from particle movement.
779
+
780
+ ### LightRenderer
781
+
782
+ ```ts
783
+ new LightRenderer(options?: Partial<LightRendererOptions>)
784
+
785
+ interface PointLightOptions {
786
+ color?: THREE.Color | string | number;
787
+ intensity?: number;
788
+ distance?: number;
789
+ decay?: number;
790
+ power?: number;
791
+ }
792
+
793
+ interface LightRendererOptions extends RendererOptions {
794
+ brightness: DynamicValue<number>;
795
+ rangeMultiplier: DynamicValue<number>;
796
+ groupingRadiusRatio: number;
797
+ decay: number;
798
+ count: number;
799
+ ratio: number;
800
+ randomDistribution: boolean;
801
+ inheritParticleColor: boolean;
802
+ sizeAffectsRange: boolean;
803
+ alphaAffectsIntensity: boolean;
804
+ lightOptions: PointLightOptions;
805
+ }
806
+ ```
807
+
808
+ Maps particles or particle groups to point lights. Use `count` and `ratio` to
809
+ control how many particles can create lights.
810
+
811
+ ## Force Fields
812
+
813
+ `ParticleForceField` is a `THREE.Object3D` that can be placed in the scene and
814
+ sampled by `ExternalForces`.
815
+
816
+ ```ts
817
+ interface ForceFieldOptions {
818
+ position: THREE.Vector3;
819
+ direction: DynamicValue<THREE.Vector3>;
820
+ gravity: DynamicValue<number>;
821
+ rotationSpeed: DynamicValue<number>;
822
+ rotationAttraction: DynamicValue<number>;
823
+ drag: DynamicValue<number>;
824
+ scale: THREE.Vector3;
825
+ geometry: THREE.BufferGeometry;
826
+ tags: StrictMultiple<Tag>;
827
+ }
828
+ ```
829
+
830
+ Helpers:
831
+
832
+ ```ts
833
+ ParticleForceField.Box(options?, ...boxGeometryArgs)
834
+ ParticleForceField.Sphere(options?, ...sphereGeometryArgs)
835
+ ParticleForceField.Cone(options?, ...coneGeometryArgs)
836
+ ParticleForceField.Torus(options?, ...torusGeometryArgs)
837
+ ```
838
+
839
+ Example:
840
+
841
+ ```ts
842
+ const attractor = ParticleForceField.Sphere({
843
+ gravity: 10,
844
+ drag: 0.2,
845
+ scale: new THREE.Vector3(4, 4, 4),
846
+ tags: "attractor",
847
+ });
848
+
849
+ scene.add(attractor);
850
+
851
+ system.addModule(
852
+ new ExternalForces({
853
+ forceFieldFilter: (field) => field.tags?.includes("attractor") ?? false,
854
+ }),
855
+ );
856
+ ```
857
+
858
+ Use `ParticleForceFieldHelper` to visualize a field while tuning.
859
+
860
+ ## Collision And Physics
861
+
862
+ The base package includes `ThreeCollisionBackend`, which raycasts against a
863
+ Three.js scene.
864
+
865
+ ```ts
866
+ interface ThreeCollisionBackendOptions {
867
+ world?: THREE.Object3D;
868
+ staticObjects?: THREE.Object3D[];
869
+ dynamicObjects?: THREE.Object3D[];
870
+
871
+ // How long must an object remain still to be considered static?
872
+ // Defaults to 2 seconds.
873
+ staticAfter?: number;
874
+
875
+ // How frequently is the dynamic octree rebuilt?
876
+ // Defaults to 1 => once per update.
877
+ timeQuality?: number;
878
+
879
+ // How frequently is the scene scanned for new objects?
880
+ // Defaults to 1 => once per update.
881
+ refreshQuality?: number;
882
+
883
+ objectFilter?: (object: THREE.Object3D) => boolean;
884
+ staticObjectFilter?: (object: THREE.Object3D) => boolean;
885
+ dynamicObjectFilter?: (object: THREE.Object3D) => boolean;
886
+ }
887
+ ```
888
+
889
+ The `ThreeCollisionBackend` uses two octrees for static and dynamic collisions.
890
+ Sorting objects into static and dynamic can be done automatically, or with
891
+ supporting identifiers passed into the constructor options. Explicitly stated
892
+ `staticObjects` and `dynamicObjects` lists are treated as the source of truth if
893
+ present.
894
+
895
+ If explicit lists are not provided, the scene is traversed and the
896
+ `staticObjectFilter`, `dynamicObjectFilter`, and `objectFilter` predicates are
897
+ used to select objects. `objectFilter` selects whether objects are included at
898
+ all but does not decide what is considered static or dynamic.
899
+
900
+ If none of these predicates are provided, the octrees are automatically
901
+ constructed from the scene traversal, with distinctions between static and
902
+ dynamic objects being made based on movement in the scene.
903
+
904
+ For external physics engines, use the extension packages:
905
+
906
+ | Package | Engine | npm |
907
+ | -------------- | ------------ | ---------------------------------------------------------------------------- |
908
+ | `@rzmps/rapier` | Rapier | [npmjs.com/package/@rzmps/rapier](https://www.npmjs.com/package/@rzmps/rapier) |
909
+ | `@rzmps/jolt` | Jolt Physics | [npmjs.com/package/@rzmps/jolt](https://www.npmjs.com/package/@rzmps/jolt) |
910
+ | `@rzmps/ammo` | Ammo.js | [npmjs.com/package/@rzmps/ammo](https://www.npmjs.com/package/@rzmps/ammo) |
911
+
912
+ ```ts
913
+ import { Collision } from "rzmps";
914
+ import { RapierCollisionBackend } from "@rzmps/rapier";
915
+
916
+ const collision = new Collision({
917
+ backend: new RapierCollisionBackend({ RAPIER, world }),
918
+ bounce: 0.6,
919
+ dampen: 0.1,
920
+ radiusScale: 1,
921
+ lifetimeLoss: 0.2,
922
+ applyImpulses: true,
923
+ });
924
+ ```
925
+
926
+ Physics backends receive an existing physics world. They do not create, step, or
927
+ own the physics simulation.
928
+
929
+ ## Custom Modules
930
+
931
+ The `Module` class is intentionally small. You can create one directly by
932
+ passing a custom particle modify function.
933
+
934
+ ### Constructor Function
935
+
936
+ ```ts
937
+ import { Module } from "rzmps";
938
+
939
+ const upwardDrift = new Module((particle, deltaTime) => {
940
+ particle.velocity.y += 0.5 * deltaTime;
941
+ }, {
942
+ priority: -1,
943
+ tags: "smoke",
944
+ });
945
+
946
+ system.addModule(upwardDrift);
947
+ ```
948
+
949
+ ### Class Extension
950
+
951
+ For more involved behavior, subclass `Module`. This is useful when a module
952
+ needs named options, cached state, dependent modules, setup work, or cleanup.
953
+
954
+ ```ts
955
+ import * as THREE from "three";
956
+ import { Module, ModuleOptions, ParticleSystem } from "rzmps";
957
+
958
+ interface GustModuleOptions extends Partial<ModuleOptions> {
959
+ strength?: number;
960
+ direction?: THREE.Vector3;
961
+ }
962
+
963
+ class GustModule extends Module {
964
+ strength: number;
965
+ direction: THREE.Vector3;
966
+
967
+ private elapsed = 0;
968
+
969
+ constructor(options: GustModuleOptions = {}) {
970
+ super((particle, deltaTime) => {
971
+ const pulse = 0.5 + Math.sin(this.elapsed * 4) * 0.5;
972
+
973
+ particle.velocity.addScaledVector(
974
+ this.direction,
975
+ this.strength * pulse * deltaTime,
976
+ );
977
+ }, options);
978
+
979
+ this.strength = options.strength ?? 1;
980
+ this.direction = options.direction ?? new THREE.Vector3(1, 0, 0);
981
+ }
982
+
983
+ prepare(system: ParticleSystem, deltaTime: number) {
984
+ this.elapsed += deltaTime;
985
+ this.direction.normalize();
986
+ }
987
+
988
+ cleanup() {
989
+ // Release external resources here.
990
+ }
991
+ }
992
+
993
+ system.addModule(
994
+ new GustModule({
995
+ direction: new THREE.Vector3(1, 0.2, 0),
996
+ tags: "smoke",
997
+ }),
998
+ );
999
+ ```
1000
+
1001
+ Use `prepare(system, deltaTime)` for once-per-frame setup, `dependents` for
1002
+ module chains that must run together, and `cleanup()` for external resources.
1003
+
1004
+ ## Developer Guide
1005
+
1006
+ Clone the repository and install dependencies:
1007
+
1008
+ ```bash
1009
+ git clone https://github.com/rzmay/rzmps.git
1010
+ cd rzmps
1011
+ npm install
1012
+ ```
1013
+
1014
+ Build the core package:
1015
+
1016
+ ```bash
1017
+ npm run build --workspace packages/rzmps
1018
+ ```
1019
+
1020
+ Run the package in watch mode:
1021
+
1022
+ ```bash
1023
+ npm run dev --workspace packages/rzmps
1024
+ ```
1025
+
1026
+ Run the demo locally:
1027
+
1028
+ ```bash
1029
+ npm start --workspace packages/demo
1030
+ ```
1031
+
1032
+ Build the demo:
1033
+
1034
+ ```bash
1035
+ npm run build --workspace packages/demo
1036
+ ```
1037
+
1038
+ Build the physics extensions:
1039
+
1040
+ ```bash
1041
+ npm run build --workspace @rzmps/rapier
1042
+ npm run build --workspace @rzmps/jolt
1043
+ npm run build --workspace @rzmps/ammo
1044
+ ```
1045
+
1046
+ ## License
1047
+
1048
+ MIT