sparcoon 0.4.5 → 0.6.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 (105) hide show
  1. package/README.md +13 -7
  2. package/dist/emitter/FXEmitter.Internal.d.ts +55 -0
  3. package/dist/emitter/FXEmitter.d.ts +49 -0
  4. package/dist/emitter/FXEmitter.js +64 -0
  5. package/dist/emitter/behavior/FXBehavior.d.ts +12 -2
  6. package/dist/emitter/behavior/FXBehavior.js +3 -2
  7. package/dist/emitter/behavior/FXBehaviorDirectionalForce.d.ts +25 -0
  8. package/dist/emitter/behavior/FXBehaviorDirectionalForce.js +74 -0
  9. package/dist/emitter/behavior/FXBehaviorPointForce.d.ts +40 -0
  10. package/dist/emitter/behavior/FXBehaviorPointForce.js +98 -0
  11. package/dist/emitter/behavior/FXBehaviorScaleOverLife.d.ts +9 -8
  12. package/dist/emitter/behavior/FXBehaviorScaleOverLife.js +12 -29
  13. package/dist/emitter/behavior/FXBehaviorTorqueDamping.d.ts +4 -3
  14. package/dist/emitter/behavior/FXBehaviorTorqueDamping.js +3 -2
  15. package/dist/emitter/behavior/FXBehaviorTorqueNoise.d.ts +4 -3
  16. package/dist/emitter/behavior/FXBehaviorTorqueNoise.js +3 -2
  17. package/dist/emitter/behavior/FXBehaviorTorqueOverLife.d.ts +17 -0
  18. package/dist/emitter/behavior/FXBehaviorTorqueOverLife.js +34 -0
  19. package/dist/emitter/behavior/FXBehaviorVelocityDamping.d.ts +3 -2
  20. package/dist/emitter/behavior/FXBehaviorVelocityDamping.js +3 -2
  21. package/dist/emitter/behavior/FXBehaviorVelocityNoise.d.ts +6 -5
  22. package/dist/emitter/behavior/FXBehaviorVelocityNoise.js +6 -5
  23. package/dist/emitter/behavior/FXBehaviorVelocityOverLife.d.ts +20 -0
  24. package/dist/emitter/behavior/FXBehaviorVelocityOverLife.js +48 -0
  25. package/dist/emitter/spawn/FXSpawn.d.ts +4 -2
  26. package/dist/emitter/spawn/FXSpawn.js +3 -2
  27. package/dist/emitter/spawn/FXSpawnBox.d.ts +8 -16
  28. package/dist/emitter/spawn/FXSpawnBox.js +23 -39
  29. package/dist/emitter/spawn/{FXSpawnRandomLifetime.d.ts → FXSpawnLifetime.d.ts} +3 -5
  30. package/dist/emitter/spawn/{FXSpawnRandomLifetime.js → FXSpawnLifetime.js} +8 -10
  31. package/dist/emitter/spawn/FXSpawnOffset.d.ts +22 -0
  32. package/dist/emitter/spawn/FXSpawnOffset.js +50 -0
  33. package/dist/emitter/spawn/FXSpawnPoint.d.ts +8 -10
  34. package/dist/emitter/spawn/FXSpawnPoint.js +20 -22
  35. package/dist/emitter/spawn/{FXSpawnRandomRotation.d.ts → FXSpawnRotation.d.ts} +3 -5
  36. package/dist/emitter/spawn/{FXSpawnRandomRotation.js → FXSpawnRotation.js} +8 -10
  37. package/dist/emitter/spawn/{FXSpawnRandomScale.d.ts → FXSpawnScale.d.ts} +4 -6
  38. package/dist/emitter/spawn/{FXSpawnRandomScale.js → FXSpawnScale.js} +12 -14
  39. package/dist/emitter/spawn/FXSpawnSphere.d.ts +11 -6
  40. package/dist/emitter/spawn/FXSpawnSphere.js +11 -6
  41. package/dist/emitter/spawn/{FXSpawnRandomTorque.d.ts → FXSpawnTorque.d.ts} +3 -5
  42. package/dist/emitter/spawn/{FXSpawnRandomTorque.js → FXSpawnTorque.js} +8 -10
  43. package/dist/emitter/spawn/FXSpawnVelocity.d.ts +32 -0
  44. package/dist/emitter/spawn/{FXSpawnRandomVelocity.js → FXSpawnVelocity.js} +26 -11
  45. package/dist/index.d.ts +25 -17
  46. package/dist/index.js +22 -17
  47. package/dist/materials/FXDiffuseMaterial/FXDiffuseMaterial.Internal.d.ts +4 -4
  48. package/dist/materials/FXDiffuseMaterial/FXDiffuseMaterial.Internal.js +2 -2
  49. package/dist/materials/FXDiffuseMaterial/FXDiffuseMaterial.d.ts +55 -5
  50. package/dist/materials/FXDiffuseMaterial/FXDiffuseMaterial.js +9 -2
  51. package/dist/materials/FXMaterial/FXDepthMaterial.Internal.d.ts +3 -3
  52. package/dist/materials/FXMaterial/FXDistanceMaterial.Internal.d.ts +3 -3
  53. package/dist/materials/FXMaterial/FXMaterial.d.ts +51 -5
  54. package/dist/materials/FXMaterial/FXMaterial.js +5 -0
  55. package/dist/materials/FXUnlitMaterial/FXUnlitMaterial.Internal.d.ts +3 -3
  56. package/dist/materials/FXUnlitMaterial/FXUnlitMaterial.d.ts +3 -3
  57. package/dist/materials/FXUnlitMaterial/FXUnlitMaterial.js +3 -3
  58. package/dist/miscellaneous/curve/FXCurve1D.Internal.d.ts +20 -0
  59. package/dist/miscellaneous/curve/FXCurve1D.d.ts +17 -0
  60. package/dist/miscellaneous/curve/FXCurve1D.js +94 -0
  61. package/dist/miscellaneous/miscellaneous.d.ts +3 -1
  62. package/dist/miscellaneous/miscellaneous.js +8 -4
  63. package/dist/miscellaneous/texture/FXTextureView.Internal.js +6 -0
  64. package/dist/miscellaneous/texture/FXTextureView.js +234 -0
  65. package/dist/nodes/color/FXNodeColor.d.ts +7 -0
  66. package/dist/nodes/color/FXNodeColor.js +11 -0
  67. package/dist/nodes/color/FXNodeColorOverLife.d.ts +25 -0
  68. package/dist/nodes/color/FXNodeColorOverLife.js +60 -0
  69. package/dist/nodes/color/FXNodeSphericalClip.d.ts +18 -0
  70. package/dist/nodes/color/FXNodeSphericalClip.js +45 -0
  71. package/dist/nodes/normal/FXNodeFlatNormal.d.ts +7 -0
  72. package/dist/nodes/normal/FXNodeFlatNormal.js +17 -0
  73. package/dist/nodes/normal/FXNodeNormal.d.ts +7 -0
  74. package/dist/nodes/normal/FXNodeNormal.js +7 -0
  75. package/dist/nodes/normal/FXNodeSphericalNormal.d.ts +7 -0
  76. package/dist/nodes/normal/{FXSphericalNormalNode.js → FXNodeSphericalNormal.js} +7 -4
  77. package/dist/nodes/texture/FXNodeAnimatedTexture.d.ts +47 -0
  78. package/dist/nodes/texture/FXNodeAnimatedTexture.js +137 -0
  79. package/dist/nodes/texture/FXNodeStaticTexture.d.ts +18 -0
  80. package/dist/nodes/texture/FXNodeStaticTexture.js +60 -0
  81. package/dist/nodes/texture/FXNodeTexture.d.ts +7 -0
  82. package/dist/nodes/texture/FXNodeTexture.js +11 -0
  83. package/package.json +3 -5
  84. package/dist/emitter/behavior/FXBehaviorDirectionalGravity.d.ts +0 -21
  85. package/dist/emitter/behavior/FXBehaviorDirectionalGravity.js +0 -51
  86. package/dist/emitter/behavior/FXBehaviorPointGravity.d.ts +0 -39
  87. package/dist/emitter/behavior/FXBehaviorPointGravity.js +0 -116
  88. package/dist/emitter/spawn/FXSpawnRandomVelocity.d.ts +0 -17
  89. package/dist/nodes/color/FXColorNode.d.ts +0 -3
  90. package/dist/nodes/color/FXColorNode.js +0 -8
  91. package/dist/nodes/color/FXColorOverLifeNode.d.ts +0 -6
  92. package/dist/nodes/color/FXColorOverLifeNode.js +0 -44
  93. package/dist/nodes/color/FXSphericalClipNode.d.ts +0 -5
  94. package/dist/nodes/color/FXSphericalClipNode.js +0 -30
  95. package/dist/nodes/normal/FXFlatNormalNode.d.ts +0 -4
  96. package/dist/nodes/normal/FXFlatNormalNode.js +0 -14
  97. package/dist/nodes/normal/FXNormalNode.d.ts +0 -3
  98. package/dist/nodes/normal/FXNormalNode.js +0 -4
  99. package/dist/nodes/normal/FXSphericalNormalNode.d.ts +0 -4
  100. package/dist/nodes/texture/FXAnimatedTextureNode.d.ts +0 -12
  101. package/dist/nodes/texture/FXAnimatedTextureNode.js +0 -88
  102. package/dist/nodes/texture/FXStaticTextureNode.d.ts +0 -6
  103. package/dist/nodes/texture/FXStaticTextureNode.js +0 -34
  104. package/dist/nodes/texture/FXTextureNode.d.ts +0 -3
  105. package/dist/nodes/texture/FXTextureNode.js +0 -8
package/README.md CHANGED
@@ -3,13 +3,19 @@
3
3
  [![npm](https://img.shields.io/npm/v/sparcoon.svg)](https://www.npmjs.com/package/sparcoon)
4
4
  [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
5
5
 
6
- Instanced-mesh particle system for Three.js. WebGL 1 compatible. **[EDITOR](https://jango-git.github.io/sparcoon/)**
6
+ Instanced-mesh particle system for Three.js.
7
+
8
+ ## Editor
9
+
10
+ Interactive visual **[EDITOR](https://jango-git.github.io/sparcoon/)**
11
+
12
+ ![Editor](assets/editor.jpg)
7
13
 
8
14
  ## API
9
15
 
10
16
  - **FXEmitter** extends `Object3D`. Manages emission, lifetime, pooling, rendering. One draw call per emitter. Optional back-to-front sorting.
11
17
  - **Spawn modules** set initial particle state: position (box/sphere/point), velocity (cone), rotation, scale, lifetime. Numeric params accept ranges.
12
- - **Behavior modules** update particles per frame: directional/point gravity, velocity/torque damping, simplex noise, scale-over-life.
18
+ - **Behavior modules** update particles per frame: directional/point force, velocity/torque damping, simplex noise, scale-over-life, velocity/torque over lifetime.
13
19
  - **Materials**: `FXUnlitMaterial` (MeshBasicMaterial), `FXDiffuseMaterial` (MeshLambertMaterial + optional subsurface scatter). Shadow support.
14
20
  - **Nodes** are composable material inputs: color-over-life, static/animated textures, normals (flat/spherical/map).
15
21
 
@@ -24,9 +30,9 @@ const LIFETIME = 6;
24
30
  const smoke = new FXEmitter(
25
31
  [
26
32
  new FXSpawnBox(areaMin, areaMax),
27
- new FXSpawnRandomLifetime({ min: LIFETIME * 0.5, max: LIFETIME }),
28
- new FXSpawnRandomScale({ min: 8, max: 16 }),
29
- new FXSpawnRandomRotation(),
33
+ new FXSpawnLifetime({ min: LIFETIME * 0.5, max: LIFETIME }),
34
+ new FXSpawnScale({ min: 8, max: 16 }),
35
+ new FXSpawnRotation(),
30
36
  ],
31
37
  [],
32
38
  new FXDiffuseMaterial({
@@ -37,13 +43,13 @@ const smoke = new FXEmitter(
37
43
  shadowSensitivity: 0.25,
38
44
  premultipliedAlpha: true,
39
45
  albedoNodes: [
40
- new FXAnimatedTextureNode({
46
+ new FXNodeAnimatedTexture({
41
47
  texture: smokeAtlas,
42
48
  rows: 8,
43
49
  columns: 8,
44
50
  interpolate: true,
45
51
  }),
46
- new FXColorOverLifeNode([
52
+ new FXNodeColorOverLife([
47
53
  new FXColor(0xffffff, 0),
48
54
  new FXColor(0xffffff, 0.4),
49
55
  new FXColor(0xffffff, 0.4),
@@ -1,20 +1,75 @@
1
1
  import type { Camera } from "three";
2
2
  import type { FXPropertyName, GLTypeInfo } from "../instancedParticle/shared";
3
3
  import type { FXEmitter } from "./FXEmitter";
4
+ /**
5
+ * Configuration options for {@link FXEmitter}
6
+ */
4
7
  export interface FXEmitterOptions {
8
+ /**
9
+ * Initial buffer size in particles; grows automatically when exceeded
10
+ *
11
+ * @defaultValue `32`
12
+ */
5
13
  expectedCapacity: number;
14
+ /**
15
+ * Growth increment added to the buffer when capacity is reached
16
+ *
17
+ * @defaultValue `32`
18
+ */
6
19
  capacityStep: number;
20
+ /**
21
+ * When `true`, {@link FXEmitter.destroy} also calls `destroy()` on all modules and the material
22
+ *
23
+ * @defaultValue `true`
24
+ */
7
25
  automaticallyDestroyModules: boolean;
26
+ /**
27
+ * Enables shadow casting
28
+ *
29
+ * @defaultValue `false`
30
+ */
8
31
  castShadow: boolean;
32
+ /**
33
+ * Enables shadow receiving
34
+ *
35
+ * @defaultValue `false`
36
+ */
9
37
  receiveShadow: boolean;
38
+ /** Camera used for back-to-front depth sorting; omit to disable sorting */
10
39
  sortCamera: Camera;
40
+ /**
41
+ * Fraction of frames on which sorting runs (`1` = every frame, `0.1` = ~every 10th frame)
42
+ *
43
+ * @defaultValue `0.1`
44
+ */
11
45
  sortFraction: number;
12
46
  }
47
+ /**
48
+ * Options for {@link FXEmitter.burst}
49
+ */
13
50
  export interface FXEmitterBurstOptions {
51
+ /**
52
+ * Delay in seconds before particles are spawned
53
+ *
54
+ * @defaultValue `0`
55
+ */
14
56
  delay: number;
15
57
  }
58
+ /**
59
+ * Options for {@link FXEmitter.play}
60
+ */
16
61
  export interface FXEmitterPlayOptions {
62
+ /**
63
+ * Delay in seconds before emission starts
64
+ *
65
+ * @defaultValue `0`
66
+ */
17
67
  delay: number;
68
+ /**
69
+ * Total emission duration in seconds
70
+ *
71
+ * @defaultValue `Infinity`
72
+ */
18
73
  duration: number;
19
74
  }
20
75
  export declare const EMITTERS: FXEmitter[];
@@ -4,11 +4,21 @@ import type { FXMaterial } from "../materials/FXMaterial/FXMaterial";
4
4
  import type { FXBehavior } from "./behavior/FXBehavior";
5
5
  import type { FXEmitterBurstOptions, FXEmitterOptions, FXEmitterPlayOptions } from "./FXEmitter.Internal";
6
6
  import type { FXSpawn } from "./spawn/FXSpawn";
7
+ /**
8
+ * Particle emitter - add to a scene to make particles visible
9
+ *
10
+ * @remarks
11
+ * Drives a pipeline of spawn modules (run once at particle birth) and behavior modules
12
+ * (run every frame). Call {@link FXEmitter.onWillRender} each frame to tick the system.
13
+ */
7
14
  export declare class FXEmitter extends Object3D {
8
15
  private readonly spawnSequence;
9
16
  private readonly behaviorSequence;
17
+ /** When `true`, {@link FXEmitter.destroy} also calls `destroy()` on all modules and the material; defaults to `true` */
10
18
  automaticallyDestroyModules: boolean;
19
+ /** Camera used for back-to-front depth sorting; `undefined` disables sorting */
11
20
  sortCamera?: Camera;
21
+ /** Fraction of frames on which sorting runs (`1` = every frame, `0.1` = ~every 10th frame); defaults to `0.1` */
12
22
  sortFraction: number;
13
23
  private readonly mesh;
14
24
  private readonly material;
@@ -17,13 +27,52 @@ export declare class FXEmitter extends Object3D {
17
27
  private readonly activePlays;
18
28
  private sortingAccumulator;
19
29
  private readonly sortingCameraWorldPosition;
30
+ /**
31
+ * @param spawnSequence - Modules executed in order when particles are born
32
+ * @param behaviorSequence - Modules executed in order every frame for all live particles
33
+ * @param material - Particle material
34
+ * @param options - Emitter configuration
35
+ */
20
36
  constructor(spawnSequence: FXSpawn[], behaviorSequence: readonly FXBehavior[], material: FXMaterial, options?: Partial<FXEmitterOptions>);
37
+ /**
38
+ * Ticks all active emitters; call once per frame before rendering
39
+ *
40
+ * @param deltaTime - Elapsed time in seconds since the last frame
41
+ */
21
42
  static onWillRender: (deltaTime: number) => void;
43
+ /** Unregisters the emitter, removes it from the scene, and disposes modules and material when {@link automaticallyDestroyModules} is `true` */
22
44
  destroy(): void;
45
+ /**
46
+ * Spawns `count` particles immediately, or after a delay when `options.delay` is set
47
+ *
48
+ * @param count - Number of particles to spawn
49
+ * @param options - Burst options
50
+ * @returns Handler that can be passed to {@link stop} to cancel a pending delayed burst
51
+ */
23
52
  burst(count: number, options?: Partial<FXEmitterBurstOptions>): number;
53
+ /**
54
+ * Starts continuous emission at the given rate
55
+ *
56
+ * @param rate - Particles per second
57
+ * @param options - Play options
58
+ * @returns Handler that can be passed to {@link stop} to cancel
59
+ */
24
60
  play(rate: number, options?: Partial<FXEmitterPlayOptions>): number;
61
+ /**
62
+ * Stops an active play or pending burst by handler; stops all when called with no arguments
63
+ *
64
+ * @param handler - Handler returned by {@link play} or {@link burst}; omit to stop everything
65
+ * @returns `true` if anything was stopped
66
+ */
25
67
  stop(handler?: number): boolean;
68
+ /**
69
+ * Simulates the emitter forward in time to avoid a cold-start on frame 0
70
+ *
71
+ * @param duration - Total time to simulate in seconds
72
+ * @param stepDuration - Maximum simulation step size in seconds. Defaults to `1/60`
73
+ */
26
74
  prewarm(duration: number, stepDuration?: number): void;
75
+ /** Kills all live particles and cancels all active plays and pending bursts */
27
76
  reset(): void;
28
77
  private tick;
29
78
  private readonly onRendering;
@@ -1,10 +1,24 @@
1
1
  import { Object3D, Vector3 } from 'three';
2
+ import { assertValidPositiveNumber, assertValidNonNegativeNumber } from '../miscellaneous/asserts.js';
2
3
  import { FXInstancedParticle } from '../instancedParticle/FXInstancedParticle.js';
3
4
  import { resolveGLSLTypeInfo } from '../instancedParticle/shared.js';
4
5
  import { BUILTIN_OFFSET_AGE, BUILTIN_OFFSET_POSITION_X, BUILTIN_OFFSET_VELOCITY_X, BUILTIN_OFFSET_POSITION_Y, BUILTIN_OFFSET_VELOCITY_Y, BUILTIN_OFFSET_POSITION_Z, BUILTIN_OFFSET_VELOCITY_Z, BUILTIN_OFFSET_ROTATION, BUILTIN_OFFSET_TORQUE, BUILTIN_OFFSET_RANDOM_A, BUILTIN_OFFSET_RANDOM_B, BUILTIN_OFFSET_RANDOM_C } from '../miscellaneous/miscellaneous.js';
5
6
  import { EMITTERS, collectProperties, EMITTER_DEFAULT_EXPECTED_CAPACITY, EMITTER_DEFAULT_CAPACITY_STEP, EMITTER_DEFAULT_CAST_SHADOW, EMITTER_DEFAULT_RECEIVE_SHADOW, EMITTER_DEFAULT_AUTOMATICALLY_DESTROY_MODULES, EMITTER_DEFAULT_SORT_FRACTION, EMITTER_DEFAULT_PREWARM_MIN_STEP_DURATION, EMITTER_DEFAULT_PREWARM_MAX_STEP_COUNT } from './FXEmitter.Internal.js';
6
7
 
8
+ /**
9
+ * Particle emitter - add to a scene to make particles visible
10
+ *
11
+ * @remarks
12
+ * Drives a pipeline of spawn modules (run once at particle birth) and behavior modules
13
+ * (run every frame). Call {@link FXEmitter.onWillRender} each frame to tick the system.
14
+ */
7
15
  class FXEmitter extends Object3D {
16
+ /**
17
+ * @param spawnSequence - Modules executed in order when particles are born
18
+ * @param behaviorSequence - Modules executed in order every frame for all live particles
19
+ * @param material - Particle material
20
+ * @param options - Emitter configuration
21
+ */
8
22
  constructor(_private_spawnSequence, _private_behaviorSequence, material, options = {}) {
9
23
  super();
10
24
  this._private_spawnSequence = _private_spawnSequence;
@@ -25,6 +39,15 @@ class FXEmitter extends Object3D {
25
39
  }
26
40
  }
27
41
  };
42
+ if (options.expectedCapacity !== undefined) {
43
+ assertValidPositiveNumber(options.expectedCapacity, "FXEmitter.constructor.options.expectedCapacity");
44
+ }
45
+ if (options.capacityStep !== undefined) {
46
+ assertValidPositiveNumber(options.capacityStep, "FXEmitter.constructor.options.capacityStep");
47
+ }
48
+ if (options.sortFraction !== undefined) {
49
+ assertValidNonNegativeNumber(options.sortFraction, "FXEmitter.constructor.options.sortFraction");
50
+ }
28
51
  const collectedProperties = {
29
52
  builtin: resolveGLSLTypeInfo("Matrix4"),
30
53
  };
@@ -48,6 +71,7 @@ class FXEmitter extends Object3D {
48
71
  this.add(this._private_mesh);
49
72
  EMITTERS.push(this);
50
73
  }
74
+ /** Unregisters the emitter, removes it from the scene, and disposes modules and material when {@link automaticallyDestroyModules} is `true` */
51
75
  destroy() {
52
76
  const index = EMITTERS.indexOf(this);
53
77
  if (index !== -1) {
@@ -64,9 +88,18 @@ class FXEmitter extends Object3D {
64
88
  }
65
89
  this.removeFromParent();
66
90
  }
91
+ /**
92
+ * Spawns `count` particles immediately, or after a delay when `options.delay` is set
93
+ *
94
+ * @param count - Number of particles to spawn
95
+ * @param options - Burst options
96
+ * @returns Handler that can be passed to {@link stop} to cancel a pending delayed burst
97
+ */
67
98
  burst(count, options = {}) {
99
+ assertValidPositiveNumber(count, "FXEmitter.burst.count");
68
100
  const handler = this._private_nextHandler++;
69
101
  const delay = options.delay ?? 0;
102
+ assertValidNonNegativeNumber(delay, "FXEmitter.burst.options.delay");
70
103
  if (delay <= 0) {
71
104
  this._private_spawnBurst(count);
72
105
  }
@@ -75,7 +108,18 @@ class FXEmitter extends Object3D {
75
108
  }
76
109
  return handler;
77
110
  }
111
+ /**
112
+ * Starts continuous emission at the given rate
113
+ *
114
+ * @param rate - Particles per second
115
+ * @param options - Play options
116
+ * @returns Handler that can be passed to {@link stop} to cancel
117
+ */
78
118
  play(rate, options = {}) {
119
+ assertValidPositiveNumber(rate, "FXEmitter.play.rate");
120
+ if (options.delay !== undefined) {
121
+ assertValidNonNegativeNumber(options.delay, "FXEmitter.play.options.delay");
122
+ }
79
123
  const handler = this._private_nextHandler++;
80
124
  this._private_activePlays.push({
81
125
  handler,
@@ -87,6 +131,12 @@ class FXEmitter extends Object3D {
87
131
  });
88
132
  return handler;
89
133
  }
134
+ /**
135
+ * Stops an active play or pending burst by handler; stops all when called with no arguments
136
+ *
137
+ * @param handler - Handler returned by {@link play} or {@link burst}; omit to stop everything
138
+ * @returns `true` if anything was stopped
139
+ */
90
140
  stop(handler) {
91
141
  if (handler === undefined) {
92
142
  const hadAnything = this._private_pendingBursts.length > 0 || this._private_activePlays.length > 0;
@@ -106,7 +156,15 @@ class FXEmitter extends Object3D {
106
156
  }
107
157
  return false;
108
158
  }
159
+ /**
160
+ * Simulates the emitter forward in time to avoid a cold-start on frame 0
161
+ *
162
+ * @param duration - Total time to simulate in seconds
163
+ * @param stepDuration - Maximum simulation step size in seconds. Defaults to `1/60`
164
+ */
109
165
  prewarm(duration, stepDuration = EMITTER_DEFAULT_PREWARM_MIN_STEP_DURATION) {
166
+ assertValidPositiveNumber(duration, "FXEmitter.prewarm.duration");
167
+ assertValidPositiveNumber(stepDuration, "FXEmitter.prewarm.stepDuration");
110
168
  const stepCount = Math.min(duration / Math.max(stepDuration, EMITTER_DEFAULT_PREWARM_MIN_STEP_DURATION), EMITTER_DEFAULT_PREWARM_MAX_STEP_COUNT);
111
169
  const step = duration / stepCount;
112
170
  let remaining = duration;
@@ -116,6 +174,7 @@ class FXEmitter extends Object3D {
116
174
  remaining -= deltaTime;
117
175
  }
118
176
  }
177
+ /** Kills all live particles and cancels all active plays and pending bursts */
119
178
  reset() {
120
179
  this._private_pendingBursts.length = 0;
121
180
  this._private_activePlays.length = 0;
@@ -213,6 +272,11 @@ class FXEmitter extends Object3D {
213
272
  }
214
273
  }
215
274
  }
275
+ /**
276
+ * Ticks all active emitters; call once per frame before rendering
277
+ *
278
+ * @param deltaTime - Elapsed time in seconds since the last frame
279
+ */
216
280
  FXEmitter.onWillRender = (deltaTime) => {
217
281
  for (const instance of EMITTERS) {
218
282
  instance._private_onRendering(deltaTime);
@@ -1,14 +1,24 @@
1
1
  import type { InstancedBufferAttribute } from "three";
2
2
  import type { FXPropertyName } from "../../instancedParticle/shared";
3
3
  /**
4
- * Base class for particle behavior modules.
4
+ * Abstract base for particle behavior modules that mutate per-particle properties each frame
5
5
  *
6
- * Behavior modules update particle properties each frame.
6
+ * @typeParam T - Map of local property keys to {@link FXPropertyName} identifiers,
7
+ * declaring which particle attributes this behavior reads or writes
7
8
  */
8
9
  export declare abstract class FXBehavior<T extends Record<string, FXPropertyName> = Record<string, FXPropertyName>> {
10
+ /** Particle attributes required by this behavior */
9
11
  abstract readonly requiredProperties: T;
12
+ /**
13
+ * Called once per frame to update all live particles
14
+ *
15
+ * @param properties - Buffer attributes keyed by `requiredProperties`
16
+ * @param instanceCount - Number of currently live particles
17
+ * @param deltaTime - Elapsed time since the last frame, in seconds
18
+ */
10
19
  abstract update(properties: {
11
20
  [K in keyof T]: InstancedBufferAttribute;
12
21
  }, instanceCount: number, deltaTime: number): void;
22
+ /** Releases any resources held by this behavior */
13
23
  destroy?(): void;
14
24
  }
@@ -1,7 +1,8 @@
1
1
  /**
2
- * Base class for particle behavior modules.
2
+ * Abstract base for particle behavior modules that mutate per-particle properties each frame
3
3
  *
4
- * Behavior modules update particle properties each frame.
4
+ * @typeParam T - Map of local property keys to {@link FXPropertyName} identifiers,
5
+ * declaring which particle attributes this behavior reads or writes
5
6
  */
6
7
  class FXBehavior {
7
8
  }
@@ -0,0 +1,25 @@
1
+ import { type Vector3Like } from "../../miscellaneous/math";
2
+ import type { FXVector3Config } from "../../miscellaneous/miscellaneous";
3
+ import { FXBehavior } from "./FXBehavior";
4
+ /**
5
+ * Applies constant acceleration to all particles
6
+ */
7
+ export declare class FXBehaviorDirectionalForce extends FXBehavior<{
8
+ builtin: "Matrix4";
9
+ }> {
10
+ private readonly directionInternal;
11
+ private magnitudeInternal;
12
+ /**
13
+ * @param direction - Direction vector (will be normalized)
14
+ * @param magnitude - Acceleration magnitude in units/second²
15
+ */
16
+ constructor(direction: Vector3Like, magnitude: number);
17
+ /** Direction vector (normalized) */
18
+ get direction(): Vector3Like;
19
+ /** Acceleration magnitude in units/second² */
20
+ get magnitude(): number;
21
+ /** Direction vector (normalized) */
22
+ set direction(value: FXVector3Config);
23
+ /** Acceleration magnitude in units/second² */
24
+ set magnitude(value: number);
25
+ }
@@ -0,0 +1,74 @@
1
+ import { Vector3 } from 'three';
2
+ import { assertValidNumber } from '../../miscellaneous/asserts.js';
3
+ import { EPSILON } from '../../miscellaneous/math.js';
4
+ import { resolveFXVector3Config, BUILTIN_OFFSET_VELOCITY_X, BUILTIN_OFFSET_VELOCITY_Y, BUILTIN_OFFSET_VELOCITY_Z } from '../../miscellaneous/miscellaneous.js';
5
+ import { FXBehavior } from './FXBehavior.js';
6
+
7
+ /**
8
+ * Applies constant acceleration to all particles
9
+ */
10
+ class FXBehaviorDirectionalForce extends FXBehavior {
11
+ /**
12
+ * @param direction - Direction vector (will be normalized)
13
+ * @param magnitude - Acceleration magnitude in units/second²
14
+ */
15
+ constructor(direction, magnitude) {
16
+ super();
17
+ /** @internal */
18
+ this.requiredProperties = { builtin: "Matrix4" };
19
+ const resolvedDirection = resolveFXVector3Config(direction);
20
+ assertValidNumber(resolvedDirection.x, "FXBehaviorDirectionalForce.direction.x");
21
+ assertValidNumber(resolvedDirection.y, "FXBehaviorDirectionalForce.direction.y");
22
+ assertValidNumber(resolvedDirection.z, "FXBehaviorDirectionalForce.direction.z");
23
+ assertValidNumber(magnitude, "FXBehaviorDirectionalForce.constructor.magnitude");
24
+ this._private_directionInternal = new Vector3(resolvedDirection.x, resolvedDirection.y, resolvedDirection.z);
25
+ if (this._private_directionInternal.lengthSq() <= EPSILON) {
26
+ throw new Error("FXBehaviorDirectionalForce.direction: vector cannot be zero");
27
+ }
28
+ this._private_directionInternal.normalize();
29
+ this._private_magnitudeInternal = magnitude;
30
+ }
31
+ /** Direction vector (normalized) */
32
+ get direction() {
33
+ return this._private_directionInternal;
34
+ }
35
+ /** Acceleration magnitude in units/second² */
36
+ get magnitude() {
37
+ return this._private_magnitudeInternal;
38
+ }
39
+ /** Direction vector (normalized) */
40
+ set direction(value) {
41
+ const resolved = resolveFXVector3Config(value);
42
+ assertValidNumber(resolved.x, "FXBehaviorDirectionalForce.direction.x");
43
+ assertValidNumber(resolved.y, "FXBehaviorDirectionalForce.direction.y");
44
+ assertValidNumber(resolved.z, "FXBehaviorDirectionalForce.direction.z");
45
+ this._private_directionInternal.set(resolved.x, resolved.y, resolved.z);
46
+ if (this._private_directionInternal.lengthSq() < EPSILON) {
47
+ throw new Error("FXBehaviorDirectionalForce.direction: vector cannot be zero");
48
+ }
49
+ this._private_directionInternal.normalize();
50
+ }
51
+ /** Acceleration magnitude in units/second² */
52
+ set magnitude(value) {
53
+ assertValidNumber(value, "FXBehaviorDirectionalForce.magnitude");
54
+ this._private_magnitudeInternal = value;
55
+ }
56
+ /** @internal */
57
+ update(properties, instanceCount, deltaTime) {
58
+ const { builtin } = properties;
59
+ const { array, itemSize } = builtin;
60
+ const scale = this._private_magnitudeInternal * deltaTime;
61
+ const offsetX = this._private_directionInternal.x * scale;
62
+ const offsetY = this._private_directionInternal.y * scale;
63
+ const offsetZ = this._private_directionInternal.z * scale;
64
+ for (let i = 0; i < instanceCount; i++) {
65
+ const itemOffset = i * itemSize;
66
+ array[itemOffset + BUILTIN_OFFSET_VELOCITY_X] += offsetX;
67
+ array[itemOffset + BUILTIN_OFFSET_VELOCITY_Y] += offsetY;
68
+ array[itemOffset + BUILTIN_OFFSET_VELOCITY_Z] += offsetZ;
69
+ }
70
+ builtin.needsUpdate = true;
71
+ }
72
+ }
73
+
74
+ export { FXBehaviorDirectionalForce };
@@ -0,0 +1,40 @@
1
+ import type { Vector3Like } from "../../miscellaneous/math";
2
+ import type { FXVector3Config } from "../../miscellaneous/miscellaneous";
3
+ import { FXBehavior } from "./FXBehavior";
4
+ /**
5
+ * Applies attraction or repulsion toward a point
6
+ *
7
+ * @remarks
8
+ * Particles closer than `threshold` are unaffected.
9
+ */
10
+ export declare class FXBehaviorPointForce extends FXBehavior<{
11
+ builtin: "Matrix4";
12
+ }> {
13
+ private centerInternal;
14
+ private strengthInternal;
15
+ private exponentInternal;
16
+ private thresholdInternal;
17
+ /**
18
+ * @param center - Force center position
19
+ * @param strength - Force multiplier
20
+ * @param exponent - Distance power exponent
21
+ * @param threshold - Minimum distance
22
+ */
23
+ constructor(center: Vector3Like, strength: number, exponent?: number, threshold?: number);
24
+ /** Force center position */
25
+ get center(): Vector3Like;
26
+ /** Force multiplier */
27
+ get strength(): number;
28
+ /** Distance power exponent */
29
+ get exponent(): number;
30
+ /** Minimum distance */
31
+ get threshold(): number;
32
+ /** Force center position */
33
+ set center(value: FXVector3Config);
34
+ /** Force multiplier */
35
+ set strength(value: number);
36
+ /** Distance power exponent */
37
+ set exponent(value: number);
38
+ /** Minimum distance */
39
+ set threshold(value: number);
40
+ }
@@ -0,0 +1,98 @@
1
+ import { assertValidNumber } from '../../miscellaneous/asserts.js';
2
+ import { resolveFXVector3Config, BUILTIN_OFFSET_POSITION_X, BUILTIN_OFFSET_POSITION_Y, BUILTIN_OFFSET_POSITION_Z, BUILTIN_OFFSET_VELOCITY_X, BUILTIN_OFFSET_VELOCITY_Y, BUILTIN_OFFSET_VELOCITY_Z } from '../../miscellaneous/miscellaneous.js';
3
+ import { FXBehavior } from './FXBehavior.js';
4
+
5
+ /**
6
+ * Applies attraction or repulsion toward a point
7
+ *
8
+ * @remarks
9
+ * Particles closer than `threshold` are unaffected.
10
+ */
11
+ class FXBehaviorPointForce extends FXBehavior {
12
+ /**
13
+ * @param center - Force center position
14
+ * @param strength - Force multiplier
15
+ * @param exponent - Distance power exponent
16
+ * @param threshold - Minimum distance
17
+ */
18
+ constructor(center, strength, exponent = 2, threshold = 0.1) {
19
+ super();
20
+ /** @internal */
21
+ this.requiredProperties = { builtin: "Matrix4" };
22
+ this._private_centerInternal = resolveFXVector3Config(center);
23
+ assertValidNumber(this._private_centerInternal.x, "FXBehaviorPointForce.constructor.center.x");
24
+ assertValidNumber(this._private_centerInternal.y, "FXBehaviorPointForce.constructor.center.y");
25
+ assertValidNumber(this._private_centerInternal.z, "FXBehaviorPointForce.constructor.center.z");
26
+ assertValidNumber(strength, "FXBehaviorPointForce.constructor.strength");
27
+ this._private_strengthInternal = strength;
28
+ assertValidNumber(exponent, "FXBehaviorPointForce.constructor.exponent");
29
+ this._private_exponentInternal = exponent;
30
+ assertValidNumber(threshold, "FXBehaviorPointForce.constructor.threshold");
31
+ this._private_thresholdInternal = threshold;
32
+ }
33
+ /** Force center position */
34
+ get center() {
35
+ return this._private_centerInternal;
36
+ }
37
+ /** Force multiplier */
38
+ get strength() {
39
+ return this._private_strengthInternal;
40
+ }
41
+ /** Distance power exponent */
42
+ get exponent() {
43
+ return this._private_exponentInternal;
44
+ }
45
+ /** Minimum distance */
46
+ get threshold() {
47
+ return this._private_thresholdInternal;
48
+ }
49
+ /** Force center position */
50
+ set center(value) {
51
+ this._private_centerInternal = resolveFXVector3Config(value);
52
+ assertValidNumber(this._private_centerInternal.x, "FXBehaviorPointForce.center.x");
53
+ assertValidNumber(this._private_centerInternal.y, "FXBehaviorPointForce.center.y");
54
+ assertValidNumber(this._private_centerInternal.z, "FXBehaviorPointForce.center.z");
55
+ }
56
+ /** Force multiplier */
57
+ set strength(value) {
58
+ assertValidNumber(value, "FXBehaviorPointForce.strength");
59
+ this._private_strengthInternal = value;
60
+ }
61
+ /** Distance power exponent */
62
+ set exponent(value) {
63
+ assertValidNumber(value, "FXBehaviorPointForce.exponent");
64
+ this._private_exponentInternal = value;
65
+ }
66
+ /** Minimum distance */
67
+ set threshold(value) {
68
+ assertValidNumber(value, "FXBehaviorPointForce.threshold");
69
+ this._private_thresholdInternal = value;
70
+ }
71
+ /** @internal */
72
+ update(properties, instanceCount, deltaTime) {
73
+ const { builtin } = properties;
74
+ const { array, itemSize } = builtin;
75
+ const thresholdSquared = this._private_thresholdInternal * this._private_thresholdInternal;
76
+ for (let i = 0; i < instanceCount; i++) {
77
+ const itemOffset = i * itemSize;
78
+ const dx = this._private_centerInternal.x - array[itemOffset + BUILTIN_OFFSET_POSITION_X];
79
+ const dy = this._private_centerInternal.y - array[itemOffset + BUILTIN_OFFSET_POSITION_Y];
80
+ const dz = this._private_centerInternal.z - array[itemOffset + BUILTIN_OFFSET_POSITION_Z];
81
+ const distanceSquared = dx * dx + dy * dy + dz * dz;
82
+ if (distanceSquared < thresholdSquared) {
83
+ continue;
84
+ }
85
+ const distance = Math.sqrt(distanceSquared);
86
+ const forceMagnitude = this._private_strengthInternal / Math.pow(distance, this._private_exponentInternal);
87
+ const directionX = dx / distance;
88
+ const directionY = dy / distance;
89
+ const directionZ = dz / distance;
90
+ array[itemOffset + BUILTIN_OFFSET_VELOCITY_X] += directionX * forceMagnitude * deltaTime;
91
+ array[itemOffset + BUILTIN_OFFSET_VELOCITY_Y] += directionY * forceMagnitude * deltaTime;
92
+ array[itemOffset + BUILTIN_OFFSET_VELOCITY_Z] += directionZ * forceMagnitude * deltaTime;
93
+ }
94
+ builtin.needsUpdate = true;
95
+ }
96
+ }
97
+
98
+ export { FXBehaviorPointForce };
@@ -1,20 +1,21 @@
1
- import type { FXAspectConfig, FXRangeConfig } from "../../miscellaneous/miscellaneous";
1
+ import type { FXCurve1DConfig } from "../../miscellaneous/curve/FXCurve1D";
2
+ import { FXCurve1D } from "../../miscellaneous/curve/FXCurve1D";
3
+ import type { FXAspectConfig, FXRange } from "../../miscellaneous/miscellaneous";
2
4
  import { FXBehavior } from "./FXBehavior";
3
5
  /**
4
- * Animates particle scale over lifetime.
5
- *
6
- * Interpolates between scale anchors based on particle age. Requires at least 2 anchors.
6
+ * Animates particle scale over lifetime using a {@link FXCurve1D}
7
7
  */
8
8
  export declare class FXBehaviorScaleOverLife extends FXBehavior<{
9
9
  builtin: "Matrix4";
10
10
  }> {
11
- private readonly scales;
11
+ /** Active scale curve */
12
+ curve: FXCurve1D<FXRange>;
12
13
  private aspectInternal;
13
14
  /**
14
- * @param scales - Scale anchor points. Each accepts number, tuple, or range object
15
- * @param aspect - Width/height ratio. Accepts number or texture
15
+ * @param curve - Scale curve; accepts a {@link FXCurve1D} instance or a `FXRange[]` shorthand
16
+ * @param aspect - Width/height ratio; must be positive. Defaults to `1`
16
17
  */
17
- constructor(scales: FXRangeConfig[], aspect?: FXAspectConfig);
18
+ constructor(curve: FXCurve1DConfig<FXRange>, aspect?: FXAspectConfig);
18
19
  /** Width/height aspect ratio */
19
20
  get aspect(): number;
20
21
  /** Width/height aspect ratio */