pixi-particles-engine 0.1.7 → 0.1.9

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 (30) hide show
  1. package/dist/esm/behaviours/alpha-curve-behaviour.js +1 -1
  2. package/dist/esm/behaviours/curved-behaviour/curve-sampler.js +1 -1
  3. package/dist/esm/behaviours/movement-behaviours/gravity-behaviour.js +1 -1
  4. package/dist/esm/behaviours/movement-behaviours/movement-curve-behaviour.js +1 -1
  5. package/dist/esm/behaviours/movement-behaviours/radial-burst-behaviour.js +1 -1
  6. package/dist/esm/behaviours/scale-curve-behaviour.js +1 -1
  7. package/dist/esm/emitter.js +1 -1
  8. package/dist/esm/index.js +18 -18
  9. package/package.json +3 -2
  10. package/src/behaviour.ts +74 -0
  11. package/src/behaviours/alpha-behaviour.ts +29 -0
  12. package/src/behaviours/alpha-curve-behaviour.ts +34 -0
  13. package/src/behaviours/curved-behaviour/curve-key-frame.ts +23 -0
  14. package/src/behaviours/curved-behaviour/curve-sampler.ts +92 -0
  15. package/src/behaviours/movement-behaviours/gravity-behaviour.ts +48 -0
  16. package/src/behaviours/movement-behaviours/movement-curve-behaviour.ts +39 -0
  17. package/src/behaviours/movement-behaviours/radial-burst-behaviour.ts +57 -0
  18. package/src/behaviours/scale-curve-behaviour.ts +36 -0
  19. package/src/behaviours/spawn-behaviours/circle-spawn-behaviour.ts +24 -0
  20. package/src/behaviours/spawn-behaviours/rectangle-spawn-behaviour.ts +27 -0
  21. package/src/behaviours/static-behaviours/static-rotation-behaviour.ts +28 -0
  22. package/src/behaviours/static-behaviours/static-scale-behaviour.ts +21 -0
  23. package/src/emitter.ts +517 -0
  24. package/src/index.ts +18 -0
  25. package/src/px-particle.ts +108 -0
  26. package/src/texture-provider.ts +66 -0
  27. package/src/texture-providers/animated-texture-provider.ts +146 -0
  28. package/src/texture-providers/single-texture-provider.ts +22 -0
  29. package/src/texture-providers/weighted-texture-provider.ts +52 -0
  30. package/src/utils.ts +13 -0
@@ -1,4 +1,4 @@
1
- import { Curve } from "./curved-behaviour/curve-sampler";
1
+ import { Curve } from "./curved-behaviour/curve-sampler.js";
2
2
  /**
3
3
  * Alpha over lifetime driven by a curve.
4
4
  *
@@ -1,4 +1,4 @@
1
- import { Utils } from "../../utils";
1
+ import { Utils } from "../../utils.js";
2
2
  /**
3
3
  * Lightweight curve sampler for keyframed 1D values over normalized time [0..1].
4
4
  *
@@ -1,4 +1,4 @@
1
- import { Curve } from "../curved-behaviour/curve-sampler";
1
+ import { Curve } from "../curved-behaviour/curve-sampler.js";
2
2
  /**
3
3
  * Applies an acceleration (gx, gy) scaled by a curve over lifetime.
4
4
  *
@@ -1,4 +1,4 @@
1
- import { Curve } from "../curved-behaviour/curve-sampler";
1
+ import { Curve } from "../curved-behaviour/curve-sampler.js";
2
2
  /**
3
3
  * Drives particle velocity directly using curves over lifetime.
4
4
  *
@@ -1,4 +1,4 @@
1
- import { Utils } from "../../utils";
1
+ import { Utils } from "../../utils.js";
2
2
  /**
3
3
  * Initializes particle velocity on spawn in a radial/cone burst.
4
4
  *
@@ -1,4 +1,4 @@
1
- import { Curve } from "./curved-behaviour/curve-sampler";
1
+ import { Curve } from "./curved-behaviour/curve-sampler.js";
2
2
  /**
3
3
  * Uniform scale over lifetime driven by a curve.
4
4
  *
@@ -1,5 +1,5 @@
1
1
  import { ParticleContainer, Ticker } from "pixi.js";
2
- import { PxParticle } from "./px-particle";
2
+ import { PxParticle } from "./px-particle.js";
3
3
  /**
4
4
  * Emitter is a ParticleContainer that owns a pool of {@link PxParticle} instances.
5
5
  *
package/dist/esm/index.js CHANGED
@@ -1,19 +1,19 @@
1
- export * from "./behaviour";
2
- export * from "./emitter";
3
- export * from "./texture-provider";
4
- export * from "./behaviours/alpha-behaviour";
5
- export * from "./behaviours/alpha-curve-behaviour";
6
- export * from "./behaviours/scale-curve-behaviour";
7
- export * from "./behaviours/curved-behaviour/curve-key-frame";
8
- export * from "./behaviours/curved-behaviour/curve-sampler";
9
- export * from "./behaviours/movement-behaviours/gravity-behaviour";
10
- export * from "./behaviours/movement-behaviours/movement-curve-behaviour";
11
- export * from "./behaviours/movement-behaviours/radial-burst-behaviour";
12
- export * from "./behaviours/spawn-behaviours/circle-spawn-behaviour";
13
- export * from "./behaviours/spawn-behaviours/rectangle-spawn-behaviour";
14
- export * from "./behaviours/static-behaviours/static-rotation-behaviour";
15
- export * from "./behaviours/static-behaviours/static-scale-behaviour";
16
- export * from "./texture-providers/animated-texture-provider";
17
- export * from "./texture-providers/single-texture-provider";
18
- export * from "./texture-providers/weighted-texture-provider";
1
+ export * from "./behaviour.js";
2
+ export * from "./emitter.js";
3
+ export * from "./texture-provider.js";
4
+ export * from "./behaviours/alpha-behaviour.js";
5
+ export * from "./behaviours/alpha-curve-behaviour.js";
6
+ export * from "./behaviours/scale-curve-behaviour.js";
7
+ export * from "./behaviours/curved-behaviour/curve-key-frame.js";
8
+ export * from "./behaviours/curved-behaviour/curve-sampler.js";
9
+ export * from "./behaviours/movement-behaviours/gravity-behaviour.js";
10
+ export * from "./behaviours/movement-behaviours/movement-curve-behaviour.js";
11
+ export * from "./behaviours/movement-behaviours/radial-burst-behaviour.js";
12
+ export * from "./behaviours/spawn-behaviours/circle-spawn-behaviour.js";
13
+ export * from "./behaviours/spawn-behaviours/rectangle-spawn-behaviour.js";
14
+ export * from "./behaviours/static-behaviours/static-rotation-behaviour.js";
15
+ export * from "./behaviours/static-behaviours/static-scale-behaviour.js";
16
+ export * from "./texture-providers/animated-texture-provider.js";
17
+ export * from "./texture-providers/single-texture-provider.js";
18
+ export * from "./texture-providers/weighted-texture-provider.js";
19
19
  //# sourceMappingURL=index.js.map
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pixi-particles-engine",
3
- "version": "0.1.7",
3
+ "version": "0.1.9",
4
4
  "description": "High-performance PixiJS v8 particle engine with behaviours and pooling",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -31,12 +31,13 @@
31
31
  },
32
32
  "files": [
33
33
  "dist",
34
+ "src",
34
35
  "README.md",
35
36
  "LICENSE"
36
37
  ],
37
38
  "scripts": {
38
39
  "build": "npm run build:esm && npm run build:cjs",
39
- "build:esm": "tsc -p tsconfig.esm.json && node scripts/write-esm-package-json.cjs",
40
+ "build:esm": "tsc -p tsconfig.esm.json && node scripts/fix-esm-specifiers.cjs && node scripts/write-esm-package-json.cjs",
40
41
  "build:cjs": "tsc -p tsconfig.cjs.json",
41
42
  "prepare": "npm run build",
42
43
  "prepublishOnly": "npm run build"
@@ -0,0 +1,74 @@
1
+ import { ParticleProperties } from "pixi.js";
2
+ import { Emitter } from "./emitter";
3
+ import { PxParticle } from "./px-particle";
4
+
5
+ /**
6
+ * A Behaviour is a modular unit of particle logic.
7
+ *
8
+ * Behaviours can:
9
+ * - Initialize particle state at spawn (velocity, alpha, scale, etc)
10
+ * - Update particle state each frame (curves, gravity, drag, color fade, etc)
11
+ * - Clean up any per-particle state when the particle is recycled
12
+ *
13
+ * Lifecycle:
14
+ * - init(emitter) is called once when the behaviour is added to an emitter
15
+ * - onSpawn(p, emitter) is called each time a particle is spawned
16
+ * - update(p, dt, emitter) is called every frame for each active particle
17
+ * - onKill(p, emitter) is called when a particle dies and returns to the pool
18
+ */
19
+ export interface Behaviour {
20
+ /**
21
+ * Execution order relative to other behaviours.
22
+ *
23
+ * Lower priority runs earlier.
24
+ * When priorities are equal, execution order is stable and follows registration order.
25
+ *
26
+ * Use priority to ensure correct ordering when behaviours depend on each other
27
+ * (e.g. apply movement first, then apply alpha/scale curves).
28
+ */
29
+ readonly priority?: number;
30
+
31
+ /**
32
+ * Dynamic property requirements for this behaviour.
33
+ *
34
+ * PixiJS ParticleContainer uses `dynamicProperties` to decide which attributes
35
+ * must be re-uploaded to the GPU each frame.
36
+ *
37
+ * If your behaviour changes a property over time that affects rendering,
38
+ * declare it here so the emitter enables the correct dynamicProperties.
39
+ *
40
+ * Examples:
41
+ * - Changing `alpha` over time → requires: { color: true }
42
+ * - Changing `scaleX/scaleY` over time → may require: { vertex: true } (depends on Pixi internals)
43
+ * - Changing `rotation` over time → requires: { rotation: true }
44
+ *
45
+ * NOTE:
46
+ * If your behaviour only sets values ON SPAWN and never changes them after,
47
+ * you usually do NOT need to require dynamic properties.
48
+ */
49
+ readonly requires?: ParticleProperties;
50
+
51
+ /**
52
+ * Optional: called once when the behaviour is registered on an emitter.
53
+ * Useful for caching references, precomputing curves, or validating options.
54
+ */
55
+ init?(emitter: Emitter): void;
56
+
57
+ /**
58
+ * Optional: called when a particle is spawned (taken from pool and activated).
59
+ * Use this to initialize per-particle state for this behaviour.
60
+ */
61
+ onSpawn?(p: PxParticle, emitter: Emitter): void;
62
+
63
+ /**
64
+ * Optional: called every frame for each active particle.
65
+ * `dt` is delta time in seconds (already clamped by the emitter).
66
+ */
67
+ update?(p: PxParticle, dt: number, emitter: Emitter): void;
68
+
69
+ /**
70
+ * Optional: called when the particle is killed and returned to the pool.
71
+ * Use this to clear any per-particle state allocated by the behaviour.
72
+ */
73
+ onKill?(p: PxParticle, emitter: Emitter): void;
74
+ }
@@ -0,0 +1,29 @@
1
+ import { PxParticle } from "../px-particle";
2
+ import { Behaviour } from "../behaviour";
3
+ import { Emitter } from "../emitter";
4
+
5
+ /**
6
+ * Linearly fades alpha from startAlpha -> endAlpha over particle lifetime.
7
+ */
8
+ export class AlphaBehaviour implements Behaviour {
9
+ public readonly requires = { color: true };
10
+
11
+ public readonly priority = 50;
12
+
13
+ public startAlpha = 1;
14
+ public endAlpha = 0;
15
+
16
+ constructor(startAlpha = 1, endAlpha = 0) {
17
+ this.startAlpha = startAlpha;
18
+ this.endAlpha = endAlpha;
19
+ }
20
+
21
+ public onSpawn(p: PxParticle, _emitter: Emitter): void {
22
+ p.alpha = this.startAlpha;
23
+ }
24
+
25
+ public update(p: PxParticle, _dt: number, _emitter: Emitter): void {
26
+ const t = p.life > 0 ? Math.min(1, Math.max(0, p.age / p.life)) : 1;
27
+ p.alpha = this.startAlpha + (this.endAlpha - this.startAlpha) * t;
28
+ }
29
+ }
@@ -0,0 +1,34 @@
1
+ import { Behaviour } from "../behaviour";
2
+ import { PxParticle } from "../px-particle";
3
+ import { CurveKeyframe, CurveOptions } from "./curved-behaviour/curve-key-frame";
4
+ import { Curve } from "./curved-behaviour/curve-sampler";
5
+ import { Emitter } from "../emitter";
6
+
7
+ /**
8
+ * Alpha over lifetime driven by a curve.
9
+ *
10
+ * Keyframes are sampled using normalized lifetime t in [0..1].
11
+ * Output is clamped to [0..1].
12
+ */
13
+ export class AlphaCurveBehaviour implements Behaviour {
14
+ /** Alpha changes over time, so ParticleContainer must treat color as dynamic. */
15
+ public readonly requires = { color: true };
16
+
17
+ public readonly priority = 50;
18
+
19
+ private curve: Curve;
20
+
21
+ constructor(keyframes: CurveKeyframe[], opts?: CurveOptions) {
22
+ // Always clamp alpha to [0..1]
23
+ this.curve = new Curve(keyframes, { ...opts, clamp: { min: 0, max: 1 } });
24
+ }
25
+
26
+ public onSpawn(p: PxParticle) {
27
+ p.alpha = this.curve.sample(0);
28
+ }
29
+
30
+ public update(p: PxParticle, _dt?: number, _emitter?: Emitter) {
31
+ const t = p.life > 0 ? p.age / p.life : 1;
32
+ p.alpha = this.curve.sample(t);
33
+ }
34
+ }
@@ -0,0 +1,23 @@
1
+ export type EaseFn = (x: number) => number;
2
+
3
+ export type CurveKeyframe = {
4
+ /** Normalized lifetime [0..1]. */
5
+ time: number;
6
+
7
+ /** Value at this time. */
8
+ value: number;
9
+
10
+ /**
11
+ * Optional easing applied to the segment starting at this keyframe,
12
+ * i.e. easing used for interpolation from this keyframe -> next keyframe.
13
+ */
14
+ ease?: EaseFn;
15
+ };
16
+
17
+ export type CurveOptions = {
18
+ /** Default easing used when a segment has no per-keyframe ease. */
19
+ defaultEase?: EaseFn;
20
+
21
+ /** Optional clamp on sampled output. */
22
+ clamp?: { min: number; max: number };
23
+ };
@@ -0,0 +1,92 @@
1
+ import { Utils } from "../../utils";
2
+ import { CurveKeyframe, EaseFn, CurveOptions } from "./curve-key-frame";
3
+
4
+ /**
5
+ * Lightweight curve sampler for keyframed 1D values over normalized time [0..1].
6
+ *
7
+ * - Keyframes are clamped to [0..1] and sorted by time.
8
+ * - Endpoints at t=0 and t=1 are ensured (added if missing).
9
+ * - Each segment can have its own easing function.
10
+ */
11
+ export class Curve {
12
+ /** Cleaned + sorted keyframes (always includes endpoints at 0 and 1). */
13
+ private keys: CurveKeyframe[];
14
+
15
+ /** Default easing used when the keyframe has no `ease`. */
16
+ private defaultEase?: EaseFn;
17
+
18
+ /** Optional output clamp. */
19
+ private clamp?: { min: number; max: number };
20
+
21
+ constructor(keyframes: CurveKeyframe[], opts?: CurveOptions) {
22
+ this.defaultEase = opts?.defaultEase;
23
+ this.clamp = opts?.clamp;
24
+
25
+ // Normalize and sort keyframes.
26
+ const cleaned: CurveKeyframe[] = (keyframes ?? [])
27
+ .map((k) => ({
28
+ time: Utils.clamp01(k.time),
29
+ value: k.value,
30
+ ease: k.ease,
31
+ }))
32
+ .sort((a, b) => a.time - b.time);
33
+
34
+ // Fallback: flat zero curve.
35
+ if (cleaned.length === 0) cleaned.push({ time: 0, value: 0 }, { time: 1, value: 0 });
36
+
37
+ // Ensure endpoints exist at t=0 and t=1.
38
+ if (cleaned[0].time !== 0) cleaned.unshift({ time: 0, value: cleaned[0].value });
39
+
40
+ if (cleaned[cleaned.length - 1].time !== 1)
41
+ cleaned.push({
42
+ time: 1,
43
+ value: cleaned[cleaned.length - 1].value,
44
+ });
45
+
46
+ this.keys = cleaned;
47
+ }
48
+
49
+ /**
50
+ * Samples the curve at normalized time t01.
51
+ * Input is clamped to [0..1].
52
+ */
53
+ public sample(t01: number): number {
54
+ const t = Utils.clamp01(t01);
55
+
56
+ // Find which segment [k0..k1] contains t.
57
+ const i = this.findSegmentIndex(t);
58
+ const k0 = this.keys[i];
59
+ const k1 = this.keys[i + 1];
60
+
61
+ const span = k1.time - k0.time;
62
+ if (span <= 0) return this.clampValue(k1.value);
63
+
64
+ // Normalize t into segment space [0..1].
65
+ let u = (t - k0.time) / span;
66
+ u = Utils.clamp01(u);
67
+
68
+ // Apply easing for this segment (if any).
69
+ const ease = k0.ease ?? this.defaultEase;
70
+ if (ease) u = Utils.clamp01(ease(u));
71
+
72
+ // Linear interpolation between segment endpoints.
73
+ const v = Utils.lerp(k0.value, k1.value, u);
74
+ return this.clampValue(v);
75
+ }
76
+
77
+ /**
78
+ * Finds the segment index i such that t is between keys[i] and keys[i+1].
79
+ */
80
+ private findSegmentIndex(t: number): number {
81
+ for (let i = 0; i < this.keys.length - 2; i++) {
82
+ if (t <= this.keys[i + 1].time) return i;
83
+ }
84
+ return this.keys.length - 2;
85
+ }
86
+
87
+ /** Applies optional output clamp. */
88
+ private clampValue(v: number): number {
89
+ if (!this.clamp) return v;
90
+ return Math.min(this.clamp.max, Math.max(this.clamp.min, v));
91
+ }
92
+ }
@@ -0,0 +1,48 @@
1
+ import { PxParticle } from "../../px-particle";
2
+ import { Behaviour } from "../../behaviour";
3
+ import { CurveKeyframe, CurveOptions } from "../curved-behaviour/curve-key-frame";
4
+ import { Curve } from "../curved-behaviour/curve-sampler";
5
+
6
+ /**
7
+ * Applies an acceleration (gx, gy) scaled by a curve over lifetime.
8
+ *
9
+ * This modifies velocity every frame:
10
+ * v += g * strength(t) * dt
11
+ *
12
+ * where t is normalized particle age in [0..1].
13
+ *
14
+ * Typical uses:
15
+ * - Gravity that ramps up/down over lifetime
16
+ * - Wind that fades in/out
17
+ * - Custom "pull" forces controlled by a curve
18
+ *
19
+ */
20
+ export class GravityCurveBehaviour implements Behaviour {
21
+ public readonly requires = { position: true };
22
+ public readonly priority = 0;
23
+
24
+ /** Acceleration vector (units: px/s² when strength is 1). */
25
+ public gx: number;
26
+ public gy: number;
27
+
28
+ /** Strength curve sampled by normalized lifetime. */
29
+ private strength: Curve;
30
+
31
+ constructor(gx: number, gy: number, strengthKeyframes: CurveKeyframe[], opts?: CurveOptions) {
32
+ this.gx = gx;
33
+ this.gy = gy;
34
+ this.strength = new Curve(strengthKeyframes, opts);
35
+ }
36
+
37
+ /**
38
+ * Called every frame for each particle.
39
+ * dt is in seconds.
40
+ */
41
+ public update(p: PxParticle, dt: number) {
42
+ const t = p.life > 0 ? p.age / p.life : 1;
43
+ const s = this.strength.sample(t);
44
+
45
+ p.vx += this.gx * s * dt;
46
+ p.vy += this.gy * s * dt;
47
+ }
48
+ }
@@ -0,0 +1,39 @@
1
+ import { PxParticle } from "../../px-particle";
2
+ import { Behaviour } from "../../behaviour";
3
+ import { CurveKeyframe, CurveOptions } from "../curved-behaviour/curve-key-frame";
4
+ import { Curve } from "../curved-behaviour/curve-sampler";
5
+
6
+ /**
7
+ * Drives particle velocity directly using curves over lifetime.
8
+ *
9
+ * Each frame:
10
+ * vx = vxCurve(t)
11
+ * vy = vyCurve(t)
12
+ *
13
+ * where t is normalized lifetime in [0..1].
14
+ *
15
+ * IMPORTANT:
16
+ * - This behaviour overwrites velocity every frame.
17
+ * - Forces like GravityCurveBehaviour should run AFTER this behaviour
18
+ * if you want gravity to modify the curve-driven motion.
19
+ *
20
+ */
21
+ export class MovementCurveBehaviour implements Behaviour {
22
+ public readonly requires = { position: true };
23
+ public readonly priority = -10;
24
+
25
+ private vxCurve: Curve;
26
+ private vyCurve: Curve;
27
+
28
+ constructor(vxKeyframes: CurveKeyframe[], vyKeyframes: CurveKeyframe[], opts?: CurveOptions) {
29
+ this.vxCurve = new Curve(vxKeyframes, opts);
30
+ this.vyCurve = new Curve(vyKeyframes, opts);
31
+ }
32
+
33
+ public update(p: PxParticle, dt: number) {
34
+ const t = p.life > 0 ? p.age / p.life : 1;
35
+
36
+ p.vx = this.vxCurve.sample(t);
37
+ p.vy = this.vyCurve.sample(t);
38
+ }
39
+ }
@@ -0,0 +1,57 @@
1
+ import { PxParticle } from "../../px-particle";
2
+ import { Behaviour } from "../../behaviour";
3
+ import { Utils } from "../../utils";
4
+ import { ParticleProperties } from "pixi.js";
5
+
6
+ /**
7
+ * Initializes particle velocity on spawn in a radial/cone burst.
8
+ *
9
+ * - Picks a random angle within [direction - spread/2, direction + spread/2]
10
+ * - Picks a random speed within [minSpeed, maxSpeed]
11
+ * - Sets particle velocity (vx, vy) in pixels/sec
12
+ *
13
+ * Notes:
14
+ * - This behaviour does NOT move particles directly; the emitter integrates vx/vy each tick.
15
+ */
16
+ export class RadialBurstBehaviour implements Behaviour {
17
+ public readonly priority = -80;
18
+
19
+ public requires: ParticleProperties = { position: true };
20
+
21
+ /** Minimum initial speed (px/sec). */
22
+ public minSpeed: number;
23
+
24
+ /** Maximum initial speed (px/sec). */
25
+ public maxSpeed: number;
26
+
27
+ /**
28
+ * Center direction in radians.
29
+ * Pixi coordinate system:
30
+ * - 0 = right
31
+ * - PI/2 = down
32
+ */
33
+ public direction: number = 0;
34
+
35
+ /**
36
+ * Cone spread in radians.
37
+ * - 2π = full circle
38
+ * - PI/3 ≈ 60° cone
39
+ */
40
+ public spread: number = Math.PI * 2;
41
+
42
+ constructor(minSpeed: number, maxSpeed: number, direction: number = 0, spread: number = Math.PI * 2) {
43
+ this.minSpeed = minSpeed;
44
+ this.maxSpeed = maxSpeed;
45
+ this.direction = direction;
46
+ this.spread = spread;
47
+ }
48
+
49
+ public onSpawn(p: PxParticle) {
50
+ const half = this.spread * 0.5;
51
+ const angle = this.direction + Utils.rand(-half, half);
52
+ const speed = Utils.rand(this.minSpeed, this.maxSpeed);
53
+
54
+ p.vx = Math.cos(angle) * speed;
55
+ p.vy = Math.sin(angle) * speed;
56
+ }
57
+ }
@@ -0,0 +1,36 @@
1
+ import { PxParticle } from "../px-particle";
2
+ import { Behaviour } from "../behaviour";
3
+ import { CurveKeyframe, CurveOptions } from "./curved-behaviour/curve-key-frame";
4
+ import { Curve } from "./curved-behaviour/curve-sampler";
5
+ import { Emitter } from "../emitter";
6
+
7
+ /**
8
+ * Uniform scale over lifetime driven by a curve.
9
+ *
10
+ * Samples a curve using normalized lifetime t in [0..1] and applies:
11
+ * scaleX = scaleY = curve(t)
12
+ */
13
+ export class ScaleCurveBehaviour implements Behaviour {
14
+ public readonly requires = { vertex: true };
15
+
16
+ public readonly priority = 50;
17
+
18
+ private curve: Curve;
19
+
20
+ constructor(keyframes: CurveKeyframe[], opts?: CurveOptions) {
21
+ this.curve = new Curve(keyframes, { ...opts });
22
+ }
23
+
24
+ public onSpawn(p: PxParticle) {
25
+ const s = this.curve.sample(0);
26
+ p.scaleX = s;
27
+ p.scaleY = s;
28
+ }
29
+
30
+ public update(p: PxParticle, _dt?: number, _emitter?: Emitter) {
31
+ const t = p.life > 0 ? p.age / p.life : 1;
32
+ const s = this.curve.sample(t);
33
+ p.scaleX = s;
34
+ p.scaleY = s;
35
+ }
36
+ }
@@ -0,0 +1,24 @@
1
+ import { PxParticle } from "../../px-particle";
2
+ import { Behaviour } from "../../behaviour";
3
+
4
+ /**
5
+ * Spawns particles uniformly within a circle centered at (0, 0).
6
+ *
7
+ * Uses sqrt(random) to achieve uniform distribution by area
8
+ * (without sqrt you'd get clustering toward the center).
9
+ *
10
+ */
11
+ export class CircleSpawnBehaviour implements Behaviour {
12
+ public readonly priority = -100;
13
+
14
+ constructor(private readonly radius: number) {}
15
+
16
+ public onSpawn(p: PxParticle) {
17
+ const angle = Math.random() * Math.PI * 2;
18
+
19
+ const r = Math.sqrt(Math.random()) * this.radius;
20
+
21
+ p.x = Math.cos(angle) * r;
22
+ p.y = Math.sin(angle) * r;
23
+ }
24
+ }
@@ -0,0 +1,27 @@
1
+ import { PxParticle } from "../../px-particle";
2
+ import { Behaviour } from "../../behaviour";
3
+
4
+ /**
5
+ * Spawns particles uniformly inside a rectangle centered at (0, 0).
6
+ * */
7
+ export class RectangleSpawnBehaviour implements Behaviour {
8
+ public readonly priority = -100;
9
+
10
+ constructor(
11
+ private readonly width: number,
12
+ private readonly height: number,
13
+ ) {}
14
+
15
+ public onSpawn(p: PxParticle) {
16
+ const hw = this.width * 0.5;
17
+ const hh = this.height * 0.5;
18
+
19
+ p.x = this.rand(-hw, hw);
20
+ p.y = this.rand(-hh, hh);
21
+ }
22
+
23
+ /** Inclusive-exclusive uniform random. */
24
+ private rand(min: number, max: number) {
25
+ return min + Math.random() * (max - min);
26
+ }
27
+ }
@@ -0,0 +1,28 @@
1
+ import { Behaviour } from "../../behaviour";
2
+ import { PxParticle } from "../../px-particle";
3
+
4
+ /**
5
+ * Sets a particle's angular velocity at spawn time.
6
+ *
7
+ * Does NOT modify rotation per frame directly.
8
+ * Instead, it initializes `angleV`, which the emitter integrates each tick.
9
+ *
10
+ */
11
+ export class StaticRotationBehaviour implements Behaviour {
12
+ public readonly priority = -60;
13
+
14
+ public readonly requires = { rotation: true };
15
+
16
+ /**
17
+ * @param speed
18
+ * - number → fixed angular velocity (radians/sec)
19
+ * - {min,max} → randomized angular velocity per particle
20
+ */
21
+ constructor(public speed: number | { min: number; max: number }) {}
22
+
23
+ public onSpawn(p: PxParticle) {
24
+ const s = typeof this.speed === "number" ? this.speed : this.speed.min + Math.random() * (this.speed.max - this.speed.min);
25
+
26
+ p.angleV = s;
27
+ }
28
+ }
@@ -0,0 +1,21 @@
1
+ import { Behaviour } from "../../behaviour";
2
+ import { PxParticle } from "../../px-particle";
3
+
4
+ /**
5
+ * Sets particle scale at spawn time.
6
+ *
7
+ */
8
+ export class StaticScaleBehaviour implements Behaviour {
9
+ public readonly priority = -60;
10
+
11
+ public spawnScale: number;
12
+
13
+ constructor(spawnScale: number) {
14
+ this.spawnScale = spawnScale;
15
+ }
16
+
17
+ public onSpawn(p: PxParticle) {
18
+ p.scaleX = this.spawnScale;
19
+ p.scaleY = this.spawnScale;
20
+ }
21
+ }