r3f-vfx 0.0.5 → 0.0.6

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.
package/dist/index.d.ts CHANGED
@@ -1,4 +1,5 @@
1
1
  import * as react from 'react';
2
+ import { RefObject, ReactNode } from 'react';
2
3
  import * as THREE from 'three/webgpu';
3
4
  import { CoreState } from 'core-vfx';
4
5
 
@@ -36,39 +37,185 @@ declare const Lighting: Readonly<{
36
37
  STANDARD: "standard";
37
38
  PHYSICAL: "physical";
38
39
  }>;
39
- declare function bakeCurveToArray(curveData: any, resolution?: number): Float32Array<ArrayBuffer>;
40
- declare function createCombinedCurveTexture(sizeCurve: any, opacityCurve: any, velocityCurve: any, rotationSpeedCurve: any): THREE.DataTexture;
41
- declare const VFXParticles: react.ForwardRefExoticComponent<react.RefAttributes<any>>;
42
-
43
- /**
44
- * Higher-order hook for programmatic emitter control
45
- *
46
- * Usage:
47
- * const { emit, burst, start, stop } = useVFXEmitter("sparks");
48
- *
49
- * // Emit at a position
50
- * emit([1, 2, 3], 50);
51
- *
52
- * // Burst with overrides
53
- * burst([0, 0, 0], 100, { colorStart: ["#ff0000"] });
54
- */
55
- declare function useVFXEmitter(name: any): {
56
- emit: (position?: number[], count?: number, overrides?: null) => boolean;
57
- burst: (position?: number[], count?: number, overrides?: null) => boolean;
58
- start: () => boolean;
59
- stop: () => boolean;
60
- clear: () => boolean;
61
- isEmitting: () => boolean;
62
- getUniforms: () => Record<string, unknown> | null;
63
- getParticles: () => {
64
- spawn: (x: number, y: number, z: number, count: number, overrides?: Record<string, unknown> | null) => void;
65
- start: () => void;
66
- stop: () => void;
67
- clear: () => void;
68
- isEmitting: boolean;
69
- uniforms: Record<string, unknown>;
40
+ type CurvePoint = {
41
+ pos: [number, number];
42
+ handleIn?: [number, number];
43
+ handleOut?: [number, number];
44
+ };
45
+ type CurveData = {
46
+ points: CurvePoint[];
47
+ } | null;
48
+ declare const bakeCurveToArray: (curveData: CurveData, resolution?: number) => Float32Array;
49
+ declare const createCombinedCurveTexture: (sizeCurve: CurveData, opacityCurve: CurveData, velocityCurve: CurveData, rotationSpeedCurve: CurveData) => THREE.DataTexture;
50
+ type Rotation3DInput = number | [number, number] | [[number, number], [number, number], [number, number]] | null | undefined;
51
+ type ParticleData = Record<string, any>;
52
+ type VFXParticlesProps = {
53
+ /** Optional name for registering with useVFXStore (enables VFXEmitter linking) */
54
+ name?: string;
55
+ /** Maximum number of particles */
56
+ maxParticles?: number;
57
+ /** Particle size [min, max] or single value */
58
+ size?: number | [number, number];
59
+ /** Array of hex color strings for start color */
60
+ colorStart?: string[];
61
+ /** Array of hex color strings for end color (null = use colorStart) */
62
+ colorEnd?: string[] | null;
63
+ /** Fade size [start, end] multiplier over lifetime */
64
+ fadeSize?: number | [number, number];
65
+ /** Curve data for size over lifetime */
66
+ fadeSizeCurve?: CurveData;
67
+ /** Fade opacity [start, end] multiplier over lifetime */
68
+ fadeOpacity?: number | [number, number];
69
+ /** Curve data for opacity over lifetime */
70
+ fadeOpacityCurve?: CurveData;
71
+ /** Curve data for velocity over lifetime */
72
+ velocityCurve?: CurveData;
73
+ /** Gravity vector [x, y, z] */
74
+ gravity?: [number, number, number];
75
+ /** Particle lifetime in seconds [min, max] or single value */
76
+ lifetime?: number | [number, number];
77
+ /** Direction ranges for velocity */
78
+ direction?: Rotation3DInput;
79
+ /** Start position offset ranges */
80
+ startPosition?: Rotation3DInput;
81
+ /** Speed [min, max] or single value */
82
+ speed?: number | [number, number];
83
+ /** Friction settings */
84
+ friction?: {
85
+ intensity?: number | [number, number];
86
+ easing?: string;
87
+ };
88
+ /** Particle appearance type */
89
+ appearance?: (typeof Appearance)[keyof typeof Appearance];
90
+ /** Alpha map texture */
91
+ alphaMap?: THREE.Texture | null;
92
+ /** Flipbook animation settings */
93
+ flipbook?: {
94
+ rows: number;
95
+ columns: number;
96
+ } | null;
97
+ /** Rotation [min, max] in radians or 3D rotation ranges */
98
+ rotation?: Rotation3DInput;
99
+ /** Rotation speed [min, max] in radians/second or 3D ranges */
100
+ rotationSpeed?: Rotation3DInput;
101
+ /** Curve data for rotation speed over lifetime */
102
+ rotationSpeedCurve?: CurveData;
103
+ /** Custom geometry for 3D particles */
104
+ geometry?: THREE.BufferGeometry | null;
105
+ /** Rotate geometry to face velocity direction */
106
+ orientToDirection?: boolean;
107
+ /** Which local axis aligns with velocity */
108
+ orientAxis?: string;
109
+ /** Stretch particles based on speed */
110
+ stretchBySpeed?: {
111
+ factor: number;
112
+ maxStretch: number;
113
+ } | null;
114
+ /** Material lighting type for geometry mode */
115
+ lighting?: (typeof Lighting)[keyof typeof Lighting];
116
+ /** Enable shadows on geometry instances */
117
+ shadow?: boolean;
118
+ /** Blending mode */
119
+ blending?: THREE.Blending;
120
+ /** Color intensity multiplier */
121
+ intensity?: number;
122
+ /** Emitter position [x, y, z] */
123
+ position?: [number, number, number];
124
+ /** Start emitting automatically */
125
+ autoStart?: boolean;
126
+ /** Delay between emissions in seconds */
127
+ delay?: number;
128
+ /** TSL node or function for backdrop sampling */
129
+ backdropNode?: any | ((data: ParticleData) => any) | null;
130
+ /** TSL node or function for custom opacity */
131
+ opacityNode?: any | ((data: ParticleData) => any) | null;
132
+ /** TSL node or function to override color */
133
+ colorNode?: any | ((data: ParticleData, defaultColor: any) => any) | null;
134
+ /** TSL node or function for alpha test/discard */
135
+ alphaTestNode?: any | ((data: ParticleData) => any) | null;
136
+ /** TSL node or function for shadow map output */
137
+ castShadowNode?: any | ((data: ParticleData) => any) | null;
138
+ /** Number of particles to emit per frame */
139
+ emitCount?: number;
140
+ /** Emitter shape type */
141
+ emitterShape?: (typeof EmitterShape)[keyof typeof EmitterShape];
142
+ /** Emitter radius [inner, outer] */
143
+ emitterRadius?: number | [number, number];
144
+ /** Cone angle in radians */
145
+ emitterAngle?: number;
146
+ /** Cone height [min, max] */
147
+ emitterHeight?: number | [number, number];
148
+ /** Emit from surface only */
149
+ emitterSurfaceOnly?: boolean;
150
+ /** Direction for cone/disk normal */
151
+ emitterDirection?: [number, number, number];
152
+ /** Turbulence settings */
153
+ turbulence?: {
154
+ intensity: number;
155
+ frequency?: number;
156
+ speed?: number;
70
157
  } | null;
158
+ /** Array of attractors (max 4) */
159
+ attractors?: Array<{
160
+ position?: [number, number, number];
161
+ strength?: number;
162
+ radius?: number;
163
+ type?: 'point' | 'vortex';
164
+ axis?: [number, number, number];
165
+ }> | null;
166
+ /** Particles move from spawn position to center over lifetime */
167
+ attractToCenter?: boolean;
168
+ /** Use start position offset as direction */
169
+ startPositionAsDirection?: boolean;
170
+ /** Fade particles when intersecting scene geometry */
171
+ softParticles?: boolean;
172
+ /** Distance over which to fade soft particles */
173
+ softDistance?: number;
174
+ /** Plane collision settings */
175
+ collision?: {
176
+ plane?: {
177
+ y: number;
178
+ };
179
+ bounce?: number;
180
+ friction?: number;
181
+ die?: boolean;
182
+ sizeBasedGravity?: number;
183
+ } | null;
184
+ /** Show debug control panel */
185
+ debug?: boolean;
71
186
  };
187
+ declare const VFXParticles: react.ForwardRefExoticComponent<VFXParticlesProps & react.RefAttributes<unknown>>;
188
+
189
+ interface VFXEmitterProps {
190
+ /** Name of the registered VFXParticles system */
191
+ name?: string;
192
+ /** Direct ref to VFXParticles (alternative to name) */
193
+ particlesRef?: RefObject<any> | any;
194
+ /** Local position offset */
195
+ position?: [number, number, number];
196
+ /** Particles to emit per burst */
197
+ emitCount?: number;
198
+ /** Seconds between emissions (0 = every frame) */
199
+ delay?: number;
200
+ /** Start emitting automatically */
201
+ autoStart?: boolean;
202
+ /** Keep emitting (false = emit once) */
203
+ loop?: boolean;
204
+ /** Transform direction by parent's world rotation */
205
+ localDirection?: boolean;
206
+ /** Direction override [[minX,maxX],[minY,maxY],[minZ,maxZ]] */
207
+ direction?: [[number, number], [number, number], [number, number]];
208
+ /** Per-spawn overrides (size, speed, colors, etc.) */
209
+ overrides?: Record<string, any> | null;
210
+ /** Callback fired after each emission */
211
+ onEmit?: (params: {
212
+ position: [number, number, number] | number[];
213
+ count: number;
214
+ direction: any;
215
+ }) => void;
216
+ /** Children elements */
217
+ children?: ReactNode;
218
+ }
72
219
  /**
73
220
  * VFXEmitter - A reusable emitter component that links to a VFXParticles system
74
221
  *
@@ -113,7 +260,36 @@ declare function useVFXEmitter(name: any): {
113
260
  * @param {object} [props.overrides] - Per-spawn overrides (size, speed, colors, etc.)
114
261
  * @param {function} [props.onEmit] - Callback fired after each emission
115
262
  */
116
- declare const VFXEmitter: react.ForwardRefExoticComponent<react.RefAttributes<any>>;
263
+ declare const VFXEmitter: react.ForwardRefExoticComponent<VFXEmitterProps & react.RefAttributes<unknown>>;
264
+ /**
265
+ * Higher-order hook for programmatic emitter control
266
+ *
267
+ * Usage:
268
+ * const { emit, burst, start, stop } = useVFXEmitter("sparks");
269
+ *
270
+ * // Emit at a position
271
+ * emit([1, 2, 3], 50);
272
+ *
273
+ * // Burst with overrides
274
+ * burst([0, 0, 0], 100, { colorStart: ["#ff0000"] });
275
+ */
276
+ declare function useVFXEmitter(name: string): {
277
+ emit: (position?: number[], count?: number, overrides?: null) => boolean;
278
+ burst: (position?: number[], count?: number, overrides?: null) => boolean;
279
+ start: () => boolean;
280
+ stop: () => boolean;
281
+ clear: () => boolean;
282
+ isEmitting: () => boolean;
283
+ getUniforms: () => Record<string, unknown> | null;
284
+ getParticles: () => {
285
+ spawn: (x: number, y: number, z: number, count: number, overrides?: Record<string, unknown> | null) => void;
286
+ start: () => void;
287
+ stop: () => void;
288
+ clear: () => void;
289
+ isEmitting: boolean;
290
+ uniforms: Record<string, unknown>;
291
+ } | null;
292
+ };
117
293
 
118
294
  declare function useVFXStore(): CoreState;
119
295
  declare function useVFXStore<T>(selector: (state: CoreState) => T): T;
package/dist/index.js CHANGED
@@ -5117,7 +5117,7 @@ ${" ".repeat(indent - 2)}}`;
5117
5117
  }
5118
5118
  });
5119
5119
 
5120
- // src/VFXParticles.jsx
5120
+ // src/VFXParticles.tsx
5121
5121
  import {
5122
5122
  forwardRef,
5123
5123
  useImperativeHandle,
@@ -5149,7 +5149,6 @@ import {
5149
5149
  positionLocal,
5150
5150
  cos,
5151
5151
  sin,
5152
- atan,
5153
5152
  sqrt,
5154
5153
  acos,
5155
5154
  PI,
@@ -5164,7 +5163,7 @@ import {
5164
5163
  import { jsx as jsx2 } from "react/jsx-runtime";
5165
5164
  var Appearance, Blending, EmitterShape, AttractorType, Easing, Lighting, MAX_ATTRACTORS, hexToRgb, toRange, easingToType, axisToNumber, CURVE_RESOLUTION, evaluateBezierSegment, sampleCurveAtX, bakeCurveToArray, createCombinedCurveTexture, toRotation3D, VFXParticles;
5166
5165
  var init_VFXParticles = __esm({
5167
- "src/VFXParticles.jsx"() {
5166
+ "src/VFXParticles.tsx"() {
5168
5167
  "use strict";
5169
5168
  init_react_store();
5170
5169
  Appearance = Object.freeze({
@@ -5388,10 +5387,11 @@ var init_VFXParticles = __esm({
5388
5387
  ];
5389
5388
  if (Array.isArray(value)) {
5390
5389
  if (Array.isArray(value[0])) {
5390
+ const nested = value;
5391
5391
  return [
5392
- toRange(value[0], [0, 0]),
5393
- toRange(value[1], [0, 0]),
5394
- toRange(value[2], [0, 0])
5392
+ toRange(nested[0], [0, 0]),
5393
+ toRange(nested[1], [0, 0]),
5394
+ toRange(nested[2], [0, 0])
5395
5395
  ];
5396
5396
  }
5397
5397
  const range = toRange(value, [0, 0]);
@@ -5510,7 +5510,7 @@ var init_VFXParticles = __esm({
5510
5510
  debug = false
5511
5511
  }, ref) {
5512
5512
  const { gl: renderer } = useThree();
5513
- const spriteRef = useRef2();
5513
+ const spriteRef = useRef2(null);
5514
5514
  const initialized = useRef2(false);
5515
5515
  const nextIndex = useRef2(0);
5516
5516
  const [emitting, setEmitting] = useState2(autoStart);
@@ -5618,8 +5618,9 @@ var init_VFXParticles = __esm({
5618
5618
  return [0, 0];
5619
5619
  }, [friction]);
5620
5620
  const frictionEasingType = useMemo(() => {
5621
+ var _a;
5621
5622
  if (typeof friction === "object" && friction !== null && "easing" in friction) {
5622
- return easingToType(friction.easing);
5623
+ return easingToType((_a = friction.easing) != null ? _a : "linear");
5623
5624
  }
5624
5625
  return 0;
5625
5626
  }, [friction]);
@@ -5850,14 +5851,15 @@ var init_VFXParticles = __esm({
5850
5851
  );
5851
5852
  for (let i = 0; i < MAX_ATTRACTORS; i++) {
5852
5853
  const a = attractorList[i];
5854
+ const u = uniforms;
5853
5855
  if (a) {
5854
- uniforms[`attractor${i}Pos`].value.set(...(_d = a.position) != null ? _d : [0, 0, 0]);
5855
- uniforms[`attractor${i}Strength`].value = (_e = a.strength) != null ? _e : 1;
5856
- uniforms[`attractor${i}Radius`].value = (_f = a.radius) != null ? _f : 0;
5857
- uniforms[`attractor${i}Type`].value = a.type === "vortex" ? 1 : 0;
5858
- uniforms[`attractor${i}Axis`].value.set(...(_g = a.axis) != null ? _g : [0, 1, 0]).normalize();
5856
+ u[`attractor${i}Pos`].value.set(...(_d = a.position) != null ? _d : [0, 0, 0]);
5857
+ u[`attractor${i}Strength`].value = (_e = a.strength) != null ? _e : 1;
5858
+ u[`attractor${i}Radius`].value = (_f = a.radius) != null ? _f : 0;
5859
+ u[`attractor${i}Type`].value = a.type === "vortex" ? 1 : 0;
5860
+ u[`attractor${i}Axis`].value.set(...(_g = a.axis) != null ? _g : [0, 1, 0]).normalize();
5859
5861
  } else {
5860
- uniforms[`attractor${i}Strength`].value = 0;
5862
+ u[`attractor${i}Strength`].value = 0;
5861
5863
  }
5862
5864
  }
5863
5865
  uniforms.attractToCenter.value = attractToCenter ? 1 : 0;
@@ -6729,6 +6731,7 @@ var init_VFXParticles = __esm({
6729
6731
  });
6730
6732
  }, [renderer, computeInit]);
6731
6733
  const applySpawnOverrides = useCallback2(
6734
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
6732
6735
  (overrides) => {
6733
6736
  if (!overrides) return null;
6734
6737
  const saved = {};
@@ -6775,15 +6778,16 @@ var init_VFXParticles = __esm({
6775
6778
  saved.gravity = uniforms.gravity.value.clone();
6776
6779
  uniforms.gravity.value.set(...overrides.gravity);
6777
6780
  }
6781
+ const u = uniforms;
6778
6782
  if (overrides.colorStart !== void 0) {
6779
6783
  const colors = overrides.colorStart.slice(0, 8).map(hexToRgb);
6780
6784
  while (colors.length < 8)
6781
6785
  colors.push(colors[colors.length - 1] || [1, 1, 1]);
6782
6786
  setUniform("colorStartCount", overrides.colorStart.length);
6783
6787
  colors.forEach((c, i) => {
6784
- if (uniforms[`colorStart${i}`]) {
6785
- saved[`colorStart${i}`] = uniforms[`colorStart${i}`].value.clone();
6786
- uniforms[`colorStart${i}`].value.setRGB(...c);
6788
+ if (u[`colorStart${i}`]) {
6789
+ saved[`colorStart${i}`] = u[`colorStart${i}`].value.clone();
6790
+ u[`colorStart${i}`].value.setRGB(...c);
6787
6791
  }
6788
6792
  });
6789
6793
  }
@@ -6793,9 +6797,9 @@ var init_VFXParticles = __esm({
6793
6797
  colors.push(colors[colors.length - 1] || [1, 1, 1]);
6794
6798
  setUniform("colorEndCount", overrides.colorEnd.length);
6795
6799
  colors.forEach((c, i) => {
6796
- if (uniforms[`colorEnd${i}`]) {
6797
- saved[`colorEnd${i}`] = uniforms[`colorEnd${i}`].value.clone();
6798
- uniforms[`colorEnd${i}`].value.setRGB(...c);
6800
+ if (u[`colorEnd${i}`]) {
6801
+ saved[`colorEnd${i}`] = u[`colorEnd${i}`].value.clone();
6802
+ u[`colorEnd${i}`].value.setRGB(...c);
6799
6803
  }
6800
6804
  });
6801
6805
  }
@@ -6836,7 +6840,8 @@ var init_VFXParticles = __esm({
6836
6840
  );
6837
6841
  const spawn = useCallback2(
6838
6842
  (x = 0, y = 0, z = 0, count = 20, overrides = null) => {
6839
- const [px, py, pz] = positionRef.current;
6843
+ var _a;
6844
+ const [px, py, pz] = (_a = positionRef.current) != null ? _a : [0, 0, 0];
6840
6845
  spawnInternal(px + x, py + y, pz + z, count, overrides);
6841
6846
  },
6842
6847
  [spawnInternal]
@@ -6935,6 +6940,7 @@ var init_VFXParticles = __esm({
6935
6940
  const prevGeometryTypeRef = useRef2(null);
6936
6941
  const prevGeometryArgsRef = useRef2(null);
6937
6942
  const handleDebugUpdate = useCallback2(
6943
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
6938
6944
  (newValues) => {
6939
6945
  var _a, _b, _c, _d, _e, _f, _g, _h, _i, _j, _k, _l, _m, _n, _o, _p, _q, _r, _s, _t, _u, _v, _w, _x, _y, _z, _A, _B, _C;
6940
6946
  debugValuesRef.current = __spreadValues(__spreadValues({}, debugValuesRef.current), newValues);
@@ -7407,7 +7413,7 @@ var init_VFXParticles = __esm({
7407
7413
  // src/index.ts
7408
7414
  init_VFXParticles();
7409
7415
 
7410
- // src/VFXEmitter.jsx
7416
+ // src/VFXEmitter.tsx
7411
7417
  init_react_store();
7412
7418
  import {
7413
7419
  useRef as useRef3,
@@ -7436,7 +7442,7 @@ var VFXEmitter = forwardRef2(function VFXEmitter2({
7436
7442
  onEmit,
7437
7443
  children
7438
7444
  }, ref) {
7439
- const groupRef = useRef3();
7445
+ const groupRef = useRef3(null);
7440
7446
  const emitAccumulator = useRef3(0);
7441
7447
  const emitting = useRef3(autoStart);
7442
7448
  const hasEmittedOnce = useRef3(false);
@@ -7446,18 +7452,28 @@ var VFXEmitter = forwardRef2(function VFXEmitter2({
7446
7452
  }
7447
7453
  return useVFXStore.getState().getParticles(name);
7448
7454
  }, [name, particlesRef]);
7449
- const transformDirectionByQuat = useCallback3((dirRange, quat) => {
7450
- if (!dirRange) return null;
7451
- const minDir = _tempVec.set(dirRange[0][0], dirRange[1][0], dirRange[2][0]);
7452
- minDir.applyQuaternion(quat);
7453
- const maxDir = new Vector32(dirRange[0][1], dirRange[1][1], dirRange[2][1]);
7454
- maxDir.applyQuaternion(quat);
7455
- return [
7456
- [Math.min(minDir.x, maxDir.x), Math.max(minDir.x, maxDir.x)],
7457
- [Math.min(minDir.y, maxDir.y), Math.max(minDir.y, maxDir.y)],
7458
- [Math.min(minDir.z, maxDir.z), Math.max(minDir.z, maxDir.z)]
7459
- ];
7460
- }, []);
7455
+ const transformDirectionByQuat = useCallback3(
7456
+ (dirRange, quat) => {
7457
+ const minDir = _tempVec.set(
7458
+ dirRange[0][0],
7459
+ dirRange[1][0],
7460
+ dirRange[2][0]
7461
+ );
7462
+ minDir.applyQuaternion(quat);
7463
+ const maxDir = new Vector32(
7464
+ dirRange[0][1],
7465
+ dirRange[1][1],
7466
+ dirRange[2][1]
7467
+ );
7468
+ maxDir.applyQuaternion(quat);
7469
+ return [
7470
+ [Math.min(minDir.x, maxDir.x), Math.max(minDir.x, maxDir.x)],
7471
+ [Math.min(minDir.y, maxDir.y), Math.max(minDir.y, maxDir.y)],
7472
+ [Math.min(minDir.z, maxDir.z), Math.max(minDir.z, maxDir.z)]
7473
+ ];
7474
+ },
7475
+ []
7476
+ );
7461
7477
  const getEmitParams = useCallback3(() => {
7462
7478
  if (!groupRef.current) {
7463
7479
  return { position, direction };
@@ -7563,7 +7579,10 @@ var VFXEmitter = forwardRef2(function VFXEmitter2({
7563
7579
  }),
7564
7580
  [emit, burst, start, stop, getParticleSystem]
7565
7581
  );
7566
- return /* @__PURE__ */ jsx3("group", { ref: groupRef, position, children });
7582
+ return (
7583
+ // @ts-expect-error
7584
+ /* @__PURE__ */ jsx3("group", { ref: groupRef, position, children })
7585
+ );
7567
7586
  });
7568
7587
  function useVFXEmitter(name) {
7569
7588
  const getParticles = useVFXStore((s) => s.getParticles);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "r3f-vfx",
3
- "version": "0.0.5",
3
+ "version": "0.0.6",
4
4
  "license": "MIT",
5
5
  "type": "module",
6
6
  "repository": {