r3f-vfx 0.1.2 → 0.1.4

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
@@ -2,7 +2,7 @@ import * as react from 'react';
2
2
  import { RefObject, ReactNode } from 'react';
3
3
  import * as THREE from 'three/webgpu';
4
4
  import { CurveData, Rotation3DInput, Appearance, Lighting, ParticleData, EmitterShape, CoreState } from 'core-vfx';
5
- export { Appearance, AttractorConfig, AttractorType, BaseParticleProps, Blending, CollisionConfig, CurveData, CurvePoint, Easing, EmitterShape, FlipbookConfig, FrictionConfig, Lighting, ParticleData, Rotation3DInput, StretchConfig, TurbulenceConfig, bakeCurveToArray, createCombinedCurveTexture } from 'core-vfx';
5
+ export { Appearance, AttractorConfig, AttractorType, BaseParticleProps, Blending, CollisionConfig, CurveChannel, CurveData, CurvePoint, CurveTextureResult, Easing, EmitterShape, FlipbookConfig, FrictionConfig, Lighting, ParticleData, Rotation3DInput, StretchConfig, TurbulenceConfig, bakeCurveToArray, buildCurveTextureBin, createCombinedCurveTexture } from 'core-vfx';
6
6
 
7
7
  type VFXParticlesProps = {
8
8
  /** Optional name for registering with useVFXStore (enables VFXEmitter linking) */
@@ -264,14 +264,24 @@ declare const useVFXStore: typeof useVFXStoreImpl & {
264
264
  getInitialState: () => CoreState;
265
265
  };
266
266
 
267
+ type CurveTextureHookResult = {
268
+ texture: THREE.DataTexture;
269
+ /** Per-channel enabled state, combining curve props and loaded .bin channels */
270
+ sizeEnabled: boolean;
271
+ opacityEnabled: boolean;
272
+ velocityEnabled: boolean;
273
+ rotationSpeedEnabled: boolean;
274
+ };
267
275
  /**
268
- * Hook for curve texture loading/baking
269
- * Returns a STABLE texture reference that updates in place
276
+ * Hook for curve texture loading/baking.
277
+ *
278
+ * If curveTexturePath is provided, loads pre-baked texture from file.
279
+ * The .bin file contains a header with a bitmask of which channels are active.
280
+ * Only those channels override the curve props; the rest use curve props or defaults.
270
281
  *
271
- * If curveTexturePath is provided, loads pre-baked texture from file
272
- * If curves are defined, bakes them synchronously on the main thread
273
- * If no curves AND no path, returns default texture (no baking needed)
282
+ * If curves are defined (no path), bakes them synchronously on the main thread.
283
+ * If no curves AND no path, returns default texture (no baking needed).
274
284
  */
275
- declare const useCurveTextureAsync: (sizeCurve: CurveData | null, opacityCurve: CurveData | null, velocityCurve: CurveData | null, rotationSpeedCurve: CurveData | null, curveTexturePath?: string | null) => THREE.DataTexture;
285
+ declare const useCurveTextureAsync: (sizeCurve: CurveData | null, opacityCurve: CurveData | null, velocityCurve: CurveData | null, rotationSpeedCurve: CurveData | null, curveTexturePath?: string | null) => CurveTextureHookResult;
276
286
 
277
- export { VFXEmitter, VFXParticles, useCurveTextureAsync, useVFXEmitter, useVFXStore };
287
+ export { type CurveTextureHookResult, VFXEmitter, VFXParticles, useCurveTextureAsync, useVFXEmitter, useVFXStore };
package/dist/index.js CHANGED
@@ -58,38 +58,29 @@ var init_react_store = __esm({
58
58
  });
59
59
 
60
60
  // src/useCurveTextureAsync.ts
61
- import { useRef, useEffect, useMemo } from "react";
61
+ import { useRef, useEffect, useMemo, useState } from "react";
62
62
  import {
63
63
  createDefaultCurveTexture,
64
64
  createCombinedCurveTexture,
65
- loadCurveTextureFromPath
65
+ loadCurveTextureFromPath,
66
+ CurveChannel
66
67
  } from "core-vfx";
67
68
  var useCurveTextureAsync;
68
69
  var init_useCurveTextureAsync = __esm({
69
70
  "src/useCurveTextureAsync.ts"() {
70
71
  "use strict";
71
72
  useCurveTextureAsync = (sizeCurve, opacityCurve, velocityCurve, rotationSpeedCurve, curveTexturePath = null) => {
72
- const textureRef = useRef(null);
73
- if (!textureRef.current) {
74
- textureRef.current = createDefaultCurveTexture();
75
- }
76
73
  const hasAnyCurve = sizeCurve || opacityCurve || velocityCurve || rotationSpeedCurve;
77
- useMemo(() => {
78
- if (!curveTexturePath && hasAnyCurve && textureRef.current) {
79
- const bakedTexture = createCombinedCurveTexture(
74
+ const texture = useMemo(() => {
75
+ if (!curveTexturePath && hasAnyCurve) {
76
+ return createCombinedCurveTexture(
80
77
  sizeCurve,
81
78
  opacityCurve,
82
79
  velocityCurve,
83
80
  rotationSpeedCurve
84
81
  );
85
- const srcData = bakedTexture.image.data;
86
- const dstData = textureRef.current.image.data;
87
- if (srcData && dstData) {
88
- dstData.set(srcData);
89
- textureRef.current.needsUpdate = true;
90
- }
91
- bakedTexture.dispose();
92
82
  }
83
+ return createDefaultCurveTexture();
93
84
  }, [
94
85
  sizeCurve,
95
86
  opacityCurve,
@@ -98,32 +89,49 @@ var init_useCurveTextureAsync = __esm({
98
89
  curveTexturePath,
99
90
  hasAnyCurve
100
91
  ]);
92
+ const [loadedTexture, setLoadedTexture] = useState(
93
+ null
94
+ );
95
+ const [loadedChannels, setLoadedChannels] = useState(0);
101
96
  useEffect(() => {
102
- if (curveTexturePath && textureRef.current) {
103
- loadCurveTextureFromPath(curveTexturePath, textureRef.current).catch(
104
- (err) => {
105
- console.warn(
106
- `Failed to load curve texture: ${curveTexturePath}, falling back to baking`,
107
- err
108
- );
109
- if (hasAnyCurve && textureRef.current) {
110
- const bakedTexture = createCombinedCurveTexture(
111
- sizeCurve,
112
- opacityCurve,
113
- velocityCurve,
114
- rotationSpeedCurve
115
- );
116
- const srcData = bakedTexture.image.data;
117
- const dstData = textureRef.current.image.data;
118
- if (srcData && dstData) {
119
- dstData.set(srcData);
120
- textureRef.current.needsUpdate = true;
121
- }
122
- bakedTexture.dispose();
123
- }
124
- }
125
- );
97
+ if (!curveTexturePath) {
98
+ setLoadedTexture(null);
99
+ setLoadedChannels(0);
100
+ return;
126
101
  }
102
+ let cancelled = false;
103
+ loadCurveTextureFromPath(curveTexturePath).then((result) => {
104
+ if (!cancelled) {
105
+ setLoadedTexture(result.texture);
106
+ setLoadedChannels(result.activeChannels);
107
+ } else {
108
+ result.texture.dispose();
109
+ }
110
+ }).catch((err) => {
111
+ console.warn(
112
+ `Failed to load curve texture: ${curveTexturePath}, falling back to baking`,
113
+ err
114
+ );
115
+ if (!cancelled && hasAnyCurve) {
116
+ setLoadedTexture(
117
+ createCombinedCurveTexture(
118
+ sizeCurve,
119
+ opacityCurve,
120
+ velocityCurve,
121
+ rotationSpeedCurve
122
+ )
123
+ );
124
+ let mask = 0;
125
+ if (sizeCurve) mask |= CurveChannel.SIZE;
126
+ if (opacityCurve) mask |= CurveChannel.OPACITY;
127
+ if (velocityCurve) mask |= CurveChannel.VELOCITY;
128
+ if (rotationSpeedCurve) mask |= CurveChannel.ROTATION_SPEED;
129
+ setLoadedChannels(mask);
130
+ }
131
+ });
132
+ return () => {
133
+ cancelled = true;
134
+ };
127
135
  }, [
128
136
  curveTexturePath,
129
137
  sizeCurve,
@@ -132,14 +140,33 @@ var init_useCurveTextureAsync = __esm({
132
140
  rotationSpeedCurve,
133
141
  hasAnyCurve
134
142
  ]);
143
+ const activeTexture = loadedTexture != null ? loadedTexture : texture;
144
+ const sizeEnabled = loadedTexture ? !!(loadedChannels & CurveChannel.SIZE) : !!sizeCurve;
145
+ const opacityEnabled = loadedTexture ? !!(loadedChannels & CurveChannel.OPACITY) : !!opacityCurve;
146
+ const velocityEnabled = loadedTexture ? !!(loadedChannels & CurveChannel.VELOCITY) : !!velocityCurve;
147
+ const rotationSpeedEnabled = loadedTexture ? !!(loadedChannels & CurveChannel.ROTATION_SPEED) : !!rotationSpeedCurve;
148
+ const prevTextureRef = useRef(null);
149
+ useEffect(() => {
150
+ const prev = prevTextureRef.current;
151
+ if (prev && prev !== activeTexture) {
152
+ prev.dispose();
153
+ }
154
+ prevTextureRef.current = activeTexture;
155
+ }, [activeTexture]);
135
156
  useEffect(() => {
136
157
  return () => {
137
158
  var _a;
138
- (_a = textureRef.current) == null ? void 0 : _a.dispose();
139
- textureRef.current = null;
159
+ (_a = prevTextureRef.current) == null ? void 0 : _a.dispose();
160
+ prevTextureRef.current = null;
140
161
  };
141
162
  }, []);
142
- return textureRef.current;
163
+ return {
164
+ texture: activeTexture,
165
+ sizeEnabled,
166
+ opacityEnabled,
167
+ velocityEnabled,
168
+ rotationSpeedEnabled
169
+ };
143
170
  };
144
171
  }
145
172
  });
@@ -156,7 +183,8 @@ __export(VFXParticlesDebugPanel_exports, {
156
183
  updateDebugPanel: () => updateDebugPanel
157
184
  });
158
185
  import { createRoot } from "react-dom/client";
159
- import { useState, useCallback, useRef as useRef2, useEffect as useEffect2 } from "react";
186
+ import { useState as useState2, useCallback, useRef as useRef2, useEffect as useEffect2 } from "react";
187
+ import { buildCurveTextureBin } from "core-vfx";
160
188
  import * as THREE from "three";
161
189
  import { create } from "zustand";
162
190
  import { Fragment, jsx, jsxs } from "react/jsx-runtime";
@@ -1220,10 +1248,10 @@ ${" ".repeat(indent - 2)}}`;
1220
1248
  onToggleEnabled,
1221
1249
  hidden = false
1222
1250
  }) => {
1223
- const [isOpen, setIsOpen] = useState(defaultOpen);
1224
- const [isHovered, setIsHovered] = useState(false);
1251
+ const [isOpen, setIsOpen] = useState2(defaultOpen);
1252
+ const [isHovered, setIsHovered] = useState2(false);
1225
1253
  const contentRef = useRef2(null);
1226
- const [contentHeight, setContentHeight] = useState(defaultOpen ? "auto" : 0);
1254
+ const [contentHeight, setContentHeight] = useState2(defaultOpen ? "auto" : 0);
1227
1255
  useEffect2(() => {
1228
1256
  if (contentRef.current) {
1229
1257
  if (isOpen) {
@@ -1305,7 +1333,7 @@ ${" ".repeat(indent - 2)}}`;
1305
1333
  const hasMoved = useRef2(false);
1306
1334
  const startX = useRef2(0);
1307
1335
  const startValue = useRef2(0);
1308
- const [isDragging, setIsDragging] = useState(false);
1336
+ const [isDragging, setIsDragging] = useState2(false);
1309
1337
  const handleMouseDown = useCallback(
1310
1338
  (e) => {
1311
1339
  if (e.button !== 0) return;
@@ -1363,8 +1391,8 @@ ${" ".repeat(indent - 2)}}`;
1363
1391
  placeholder
1364
1392
  }) => {
1365
1393
  const inputRef = useRef2(null);
1366
- const [localValue, setLocalValue] = useState(String(value));
1367
- const [isFocused, setIsFocused] = useState(false);
1394
+ const [localValue, setLocalValue] = useState2(String(value));
1395
+ const [isFocused, setIsFocused] = useState2(false);
1368
1396
  const { handleMouseDown, hasMoved, isDragging } = useScrubber(
1369
1397
  value,
1370
1398
  onChange,
@@ -1491,7 +1519,7 @@ ${" ".repeat(indent - 2)}}`;
1491
1519
  step = 0.01
1492
1520
  }) => {
1493
1521
  const [minVal, maxVal] = parseRange(value, [0, 0]);
1494
- const [linked, setLinked] = useState(false);
1522
+ const [linked, setLinked] = useState2(false);
1495
1523
  const handleMinChange = useCallback(
1496
1524
  (v) => {
1497
1525
  if (linked) {
@@ -1742,8 +1770,8 @@ ${" ".repeat(indent - 2)}}`;
1742
1770
  label
1743
1771
  ] }) });
1744
1772
  CustomColorPicker = ({ color, onChange }) => {
1745
- const [isOpen, setIsOpen] = useState(false);
1746
- const [hexInput, setHexInput] = useState(color);
1773
+ const [isOpen, setIsOpen] = useState2(false);
1774
+ const [hexInput, setHexInput] = useState2(color);
1747
1775
  const pickerRef = useRef2(null);
1748
1776
  const gradientRef = useRef2(null);
1749
1777
  const isDraggingGradient = useRef2(false);
@@ -1839,7 +1867,7 @@ ${" ".repeat(indent - 2)}}`;
1839
1867
  const toHex = (x) => Math.round(x * 255).toString(16).padStart(2, "0");
1840
1868
  return `#${toHex(r)}${toHex(g)}${toHex(b)}`;
1841
1869
  };
1842
- const [hsv, setHsv] = useState(() => hexToHsv(color));
1870
+ const [hsv, setHsv] = useState2(() => hexToHsv(color));
1843
1871
  useEffect2(() => {
1844
1872
  if (!isOpen) {
1845
1873
  setHsv(hexToHsv(color));
@@ -2180,12 +2208,12 @@ ${" ".repeat(indent - 2)}}`;
2180
2208
  const containerRef = useRef2(null);
2181
2209
  const draggingRef = useRef2(null);
2182
2210
  const localDataRef = useRef2((value == null ? void 0 : value.points) || defaultValue.points);
2183
- const [, forceUpdate] = useState(0);
2184
- const [hoverItem, setHoverItem] = useState(null);
2185
- const [selectedPoint, setSelectedPoint] = useState(null);
2186
- const [isScaling, setIsScaling] = useState(false);
2211
+ const [, forceUpdate] = useState2(0);
2212
+ const [hoverItem, setHoverItem] = useState2(null);
2213
+ const [selectedPoint, setSelectedPoint] = useState2(null);
2214
+ const [isScaling, setIsScaling] = useState2(false);
2187
2215
  const scaleStartRef = useRef2(null);
2188
- const [isRotating, setIsRotating] = useState(false);
2216
+ const [isRotating, setIsRotating] = useState2(false);
2189
2217
  const rotateStartRef = useRef2(null);
2190
2218
  const SIZE = 260;
2191
2219
  const PADDING = 30;
@@ -2815,8 +2843,8 @@ ${" ".repeat(indent - 2)}}`;
2815
2843
  ]
2816
2844
  }
2817
2845
  ];
2818
- const [easingIntensity, setEasingIntensity] = useState(1);
2819
- const [easingFrequency, setEasingFrequency] = useState(3);
2846
+ const [easingIntensity, setEasingIntensity] = useState2(1);
2847
+ const [easingFrequency, setEasingFrequency] = useState2(3);
2820
2848
  const generateBounce = useCallback((intensity, frequency) => {
2821
2849
  const points2 = [{ pos: [0, 0], handleOut: [0.1, 0] }];
2822
2850
  const bounces = Math.max(1, Math.round(frequency));
@@ -3525,22 +3553,22 @@ ${" ".repeat(indent - 2)}}`;
3525
3553
  );
3526
3554
  DebugPanelContent = ({ initialValues, onUpdate }) => {
3527
3555
  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, _D, _E, _F, _G, _H, _I, _J, _K, _L, _M, _N, _O, _P, _Q, _R, _S, _T, _U, _V, _W, _X;
3528
- const [isMinimized, setIsMinimized] = useState(false);
3529
- const [panelSize, setPanelSize] = useState({ width: 380, height: null });
3530
- const [copySuccess, setCopySuccess] = useState(false);
3531
- const [bakeSuccess, setBakeSuccess] = useState(false);
3532
- const [hasPendingChanges, setHasPendingChanges] = useState(false);
3556
+ const [isMinimized, setIsMinimized] = useState2(false);
3557
+ const [panelSize, setPanelSize] = useState2({ width: 380, height: null });
3558
+ const [copySuccess, setCopySuccess] = useState2(false);
3559
+ const [bakeSuccess, setBakeSuccess] = useState2(false);
3560
+ const [hasPendingChanges, setHasPendingChanges] = useState2(false);
3533
3561
  const valuesRef = useRef2(initialValues);
3534
3562
  const dirtyKeysRef = useRef2(/* @__PURE__ */ new Set());
3535
- const [, forceUpdate] = useState(0);
3563
+ const [, forceUpdate] = useState2(0);
3536
3564
  const isResizing = useRef2(false);
3537
3565
  const resizeType = useRef2(null);
3538
3566
  const debounceTimerRef = useRef2(null);
3539
3567
  const DEBOUNCE_DELAY = 500;
3540
- const [searchQuery, setSearchQuery] = useState("");
3568
+ const [searchQuery, setSearchQuery] = useState2("");
3541
3569
  const historyRef = useRef2([JSON.parse(JSON.stringify(initialValues))]);
3542
- const [historyIndex, setHistoryIndex] = useState(0);
3543
- const [historyLength, setHistoryLength] = useState(1);
3570
+ const [historyIndex, setHistoryIndex] = useState2(0);
3571
+ const [historyLength, setHistoryLength] = useState2(1);
3544
3572
  const MAX_HISTORY = 50;
3545
3573
  const isUndoingRef = useRef2(false);
3546
3574
  const prevInitialValuesRef = useRef2(initialValues);
@@ -3606,98 +3634,15 @@ ${" ".repeat(indent - 2)}}`;
3606
3634
  flushChanges();
3607
3635
  }
3608
3636
  const values2 = valuesRef.current;
3609
- const CURVE_RESOLUTION = 256;
3610
- const evaluateBezierSegment = (t, p0, p1, h0Out, h1In) => {
3611
- const cp0 = p0;
3612
- const cp1 = [p0[0] + ((h0Out == null ? void 0 : h0Out[0]) || 0), p0[1] + ((h0Out == null ? void 0 : h0Out[1]) || 0)];
3613
- const cp2 = [p1[0] + ((h1In == null ? void 0 : h1In[0]) || 0), p1[1] + ((h1In == null ? void 0 : h1In[1]) || 0)];
3614
- const cp3 = p1;
3615
- const mt = 1 - t;
3616
- const mt2 = mt * mt;
3617
- const mt3 = mt2 * mt;
3618
- const t2 = t * t;
3619
- const t3 = t2 * t;
3620
- return [
3621
- mt3 * cp0[0] + 3 * mt2 * t * cp1[0] + 3 * mt * t2 * cp2[0] + t3 * cp3[0],
3622
- mt3 * cp0[1] + 3 * mt2 * t * cp1[1] + 3 * mt * t2 * cp2[1] + t3 * cp3[1]
3623
- ];
3624
- };
3625
- const sampleCurveAtX = (x, points) => {
3626
- var _a2, _b2, _c2, _d2;
3627
- if (!points || points.length < 2) return x;
3628
- if (!((_a2 = points[0]) == null ? void 0 : _a2.pos) || !((_b2 = points[points.length - 1]) == null ? void 0 : _b2.pos)) return x;
3629
- let segmentIdx = 0;
3630
- for (let i = 0; i < points.length - 1; i++) {
3631
- if (((_c2 = points[i]) == null ? void 0 : _c2.pos) && ((_d2 = points[i + 1]) == null ? void 0 : _d2.pos) && x >= points[i].pos[0] && x <= points[i + 1].pos[0]) {
3632
- segmentIdx = i;
3633
- break;
3634
- }
3635
- }
3636
- const p0 = points[segmentIdx];
3637
- const p1 = points[segmentIdx + 1];
3638
- if (!(p0 == null ? void 0 : p0.pos) || !(p1 == null ? void 0 : p1.pos)) return x;
3639
- let tLow = 0, tHigh = 1, t = 0.5;
3640
- for (let iter = 0; iter < 20; iter++) {
3641
- const [px] = evaluateBezierSegment(
3642
- t,
3643
- p0.pos,
3644
- p1.pos,
3645
- p0.handleOut,
3646
- p1.handleIn
3647
- );
3648
- if (Math.abs(px - x) < 1e-4) break;
3649
- if (px < x) {
3650
- tLow = t;
3651
- } else {
3652
- tHigh = t;
3653
- }
3654
- t = (tLow + tHigh) / 2;
3655
- }
3656
- const [, py] = evaluateBezierSegment(
3657
- t,
3658
- p0.pos,
3659
- p1.pos,
3660
- p0.handleOut,
3661
- p1.handleIn
3662
- );
3663
- return Math.max(-0.5, Math.min(1.5, py));
3664
- };
3665
- const bakeCurveToArray2 = (curveData) => {
3666
- const data = new Float32Array(CURVE_RESOLUTION);
3667
- if (!(curveData == null ? void 0 : curveData.points) || !Array.isArray(curveData.points) || curveData.points.length < 2) {
3668
- for (let i = 0; i < CURVE_RESOLUTION; i++) {
3669
- data[i] = 1 - i / (CURVE_RESOLUTION - 1);
3670
- }
3671
- return data;
3672
- }
3673
- const firstPoint = curveData.points[0];
3674
- const lastPoint = curveData.points[curveData.points.length - 1];
3675
- if (!(firstPoint == null ? void 0 : firstPoint.pos) || !(lastPoint == null ? void 0 : lastPoint.pos) || !Array.isArray(firstPoint.pos) || !Array.isArray(lastPoint.pos)) {
3676
- for (let i = 0; i < CURVE_RESOLUTION; i++) {
3677
- data[i] = 1 - i / (CURVE_RESOLUTION - 1);
3678
- }
3679
- return data;
3680
- }
3681
- for (let i = 0; i < CURVE_RESOLUTION; i++) {
3682
- const x = i / (CURVE_RESOLUTION - 1);
3683
- data[i] = sampleCurveAtX(x, curveData.points);
3684
- }
3685
- return data;
3686
- };
3687
- const sizeData = bakeCurveToArray2(values2.fadeSizeCurve);
3688
- const opacityData = bakeCurveToArray2(values2.fadeOpacityCurve);
3689
- const velocityData = bakeCurveToArray2(values2.velocityCurve);
3690
- const rotationSpeedData = bakeCurveToArray2(values2.rotationSpeedCurve);
3691
- const rgba = new Float32Array(CURVE_RESOLUTION * 4);
3692
- for (let i = 0; i < CURVE_RESOLUTION; i++) {
3693
- rgba[i * 4] = sizeData[i];
3694
- rgba[i * 4 + 1] = opacityData[i];
3695
- rgba[i * 4 + 2] = velocityData[i];
3696
- rgba[i * 4 + 3] = rotationSpeedData[i];
3697
- }
3637
+ const binBuffer = buildCurveTextureBin(
3638
+ values2.fadeSizeCurve || null,
3639
+ values2.fadeOpacityCurve || null,
3640
+ values2.velocityCurve || null,
3641
+ values2.rotationSpeedCurve || null
3642
+ );
3698
3643
  const vfxName = values2.name || `vfx-curves-${Date.now()}`;
3699
3644
  const filename = `${vfxName.replace(/[^a-zA-Z0-9-_]/g, "-")}.bin`;
3700
- const blob = new Blob([rgba.buffer], { type: "application/octet-stream" });
3645
+ const blob = new Blob([binBuffer], { type: "application/octet-stream" });
3701
3646
  const link = document.createElement("a");
3702
3647
  link.download = filename;
3703
3648
  link.href = URL.createObjectURL(blob);
@@ -5404,7 +5349,7 @@ import {
5404
5349
  useRef as useRef3,
5405
5350
  useMemo as useMemo2,
5406
5351
  useCallback as useCallback2,
5407
- useState as useState2
5352
+ useState as useState3
5408
5353
  } from "react";
5409
5354
  import { useFrame, useThree } from "@react-three/fiber";
5410
5355
  import * as THREE2 from "three/webgpu";
@@ -5434,7 +5379,9 @@ import {
5434
5379
  Easing as Easing2,
5435
5380
  Lighting,
5436
5381
  bakeCurveToArray,
5437
- createCombinedCurveTexture as createCombinedCurveTexture3
5382
+ createCombinedCurveTexture as createCombinedCurveTexture3,
5383
+ buildCurveTextureBin as buildCurveTextureBin2,
5384
+ CurveChannel as CurveChannel2
5438
5385
  } from "core-vfx";
5439
5386
  import { jsx as jsx2 } from "react/jsx-runtime";
5440
5387
  var VFXParticles;
@@ -5561,29 +5508,29 @@ var init_VFXParticles = __esm({
5561
5508
  const spriteRef = useRef3(null);
5562
5509
  const initialized = useRef3(false);
5563
5510
  const nextIndex = useRef3(0);
5564
- const [emitting, setEmitting] = useState2(autoStart);
5511
+ const [emitting, setEmitting] = useState3(autoStart);
5565
5512
  const emitAccumulator = useRef3(0);
5566
5513
  const delayRef = useRef3(delay);
5567
5514
  const emitCountRef = useRef3(emitCount);
5568
5515
  const turbulenceRef = useRef3(turbulence);
5569
- const [activeMaxParticles, setActiveMaxParticles] = useState2(maxParticles);
5570
- const [activeLighting, setActiveLighting] = useState2(lighting);
5571
- const [activeAppearance, setActiveAppearance] = useState2(appearance);
5572
- const [activeOrientToDirection, setActiveOrientToDirection] = useState2(orientToDirection);
5573
- const [activeGeometry, setActiveGeometry] = useState2(geometry);
5574
- const [activeShadow, setActiveShadow] = useState2(shadow);
5575
- const [activeFadeSizeCurve, setActiveFadeSizeCurve] = useState2(fadeSizeCurve);
5576
- const [activeFadeOpacityCurve, setActiveFadeOpacityCurve] = useState2(fadeOpacityCurve);
5577
- const [activeVelocityCurve, setActiveVelocityCurve] = useState2(velocityCurve);
5578
- const [activeRotationSpeedCurve, setActiveRotationSpeedCurve] = useState2(rotationSpeedCurve);
5579
- const [activeTurbulence, setActiveTurbulence] = useState2(
5516
+ const [activeMaxParticles, setActiveMaxParticles] = useState3(maxParticles);
5517
+ const [activeLighting, setActiveLighting] = useState3(lighting);
5518
+ const [activeAppearance, setActiveAppearance] = useState3(appearance);
5519
+ const [activeOrientToDirection, setActiveOrientToDirection] = useState3(orientToDirection);
5520
+ const [activeGeometry, setActiveGeometry] = useState3(geometry);
5521
+ const [activeShadow, setActiveShadow] = useState3(shadow);
5522
+ const [activeFadeSizeCurve, setActiveFadeSizeCurve] = useState3(fadeSizeCurve);
5523
+ const [activeFadeOpacityCurve, setActiveFadeOpacityCurve] = useState3(fadeOpacityCurve);
5524
+ const [activeVelocityCurve, setActiveVelocityCurve] = useState3(velocityCurve);
5525
+ const [activeRotationSpeedCurve, setActiveRotationSpeedCurve] = useState3(rotationSpeedCurve);
5526
+ const [activeTurbulence, setActiveTurbulence] = useState3(
5580
5527
  turbulence !== null && ((_a = turbulence == null ? void 0 : turbulence.intensity) != null ? _a : 0) > 0
5581
5528
  );
5582
- const [activeAttractors, setActiveAttractors] = useState2(
5529
+ const [activeAttractors, setActiveAttractors] = useState3(
5583
5530
  attractors !== null && attractors.length > 0
5584
5531
  );
5585
- const [activeCollision, setActiveCollision] = useState2(collision !== null);
5586
- const [activeNeedsPerParticleColor, setActiveNeedsPerParticleColor] = useState2(colorStart.length > 1 || colorEnd !== null);
5532
+ const [activeCollision, setActiveCollision] = useState3(collision !== null);
5533
+ const [activeNeedsPerParticleColor, setActiveNeedsPerParticleColor] = useState3(colorStart.length > 1 || colorEnd !== null);
5587
5534
  const isNonDefaultRotation = (r) => {
5588
5535
  if (typeof r === "number") return r !== 0;
5589
5536
  if (Array.isArray(r) && r.length === 2 && typeof r[0] === "number") {
@@ -5596,7 +5543,7 @@ var init_VFXParticles = __esm({
5596
5543
  }
5597
5544
  return false;
5598
5545
  };
5599
- const [activeNeedsRotation, setActiveNeedsRotation] = useState2(
5546
+ const [activeNeedsRotation, setActiveNeedsRotation] = useState3(
5600
5547
  isNonDefaultRotation(rotation) || isNonDefaultRotation(rotationSpeed)
5601
5548
  );
5602
5549
  useEffect3(() => {
@@ -5656,22 +5603,19 @@ var init_VFXParticles = __esm({
5656
5603
  () => toRange(fadeOpacity, [1, 0]),
5657
5604
  [fadeOpacity]
5658
5605
  );
5659
- const curveTexture = useCurveTextureAsync(
5606
+ const {
5607
+ texture: curveTexture,
5608
+ sizeEnabled: curveTextureSizeEnabled,
5609
+ opacityEnabled: curveTextureOpacityEnabled,
5610
+ velocityEnabled: curveTextureVelocityEnabled,
5611
+ rotationSpeedEnabled: curveTextureRotationSpeedEnabled
5612
+ } = useCurveTextureAsync(
5660
5613
  activeFadeSizeCurve,
5661
5614
  activeFadeOpacityCurve,
5662
5615
  activeVelocityCurve,
5663
5616
  activeRotationSpeedCurve,
5664
5617
  curveTexturePath
5665
5618
  );
5666
- const prevCurveTextureRef = useRef3(null);
5667
- useEffect3(() => {
5668
- prevCurveTextureRef.current = curveTexture;
5669
- return () => {
5670
- if (curveTexture) {
5671
- curveTexture.dispose();
5672
- }
5673
- };
5674
- }, [curveTexture]);
5675
5619
  const lifetimeRange = useMemo2(() => toRange(lifetime, [1, 2]), [lifetime]);
5676
5620
  const rotation3D = useMemo2(() => toRotation3D(rotation), [rotation]);
5677
5621
  const rotationSpeed3D = useMemo2(
@@ -5853,23 +5797,11 @@ var init_VFXParticles = __esm({
5853
5797
  // Soft particles
5854
5798
  softParticlesEnabled: uniform(softParticles ? 1 : 0),
5855
5799
  softDistance: uniform(softDistance),
5856
- // Velocity curve (replaces friction when enabled)
5857
- // Enable if velocityCurve prop is set OR curveTexturePath is provided
5858
- velocityCurveEnabled: uniform(
5859
- velocityCurve || curveTexturePath ? 1 : 0
5860
- ),
5861
- // Rotation speed curve (modulates rotation speed over lifetime)
5862
- rotationSpeedCurveEnabled: uniform(
5863
- rotationSpeedCurve || curveTexturePath ? 1 : 0
5864
- ),
5865
- // Fade size curve (when disabled, uses fadeSize prop interpolation)
5866
- fadeSizeCurveEnabled: uniform(
5867
- fadeSizeCurve || curveTexturePath ? 1 : 0
5868
- ),
5869
- // Fade opacity curve (when disabled, uses fadeOpacity prop interpolation)
5870
- fadeOpacityCurveEnabled: uniform(
5871
- fadeOpacityCurve || curveTexturePath ? 1 : 0
5872
- ),
5800
+ // Curve enabled flags (set by useCurveTextureAsync per-channel info)
5801
+ velocityCurveEnabled: uniform(0),
5802
+ rotationSpeedCurveEnabled: uniform(0),
5803
+ fadeSizeCurveEnabled: uniform(0),
5804
+ fadeOpacityCurveEnabled: uniform(0),
5873
5805
  // Orient axis: 0=+X, 1=+Y, 2=+Z, 3=-X, 4=-Y, 5=-Z
5874
5806
  orientAxisType: uniform(axisToNumber(orientAxis)),
5875
5807
  // Stretch by speed (uses effective velocity after curve modifier)
@@ -5980,10 +5912,10 @@ var init_VFXParticles = __esm({
5980
5912
  uniforms.startPositionAsDirection.value = startPositionAsDirection ? 1 : 0;
5981
5913
  uniforms.softParticlesEnabled.value = softParticles ? 1 : 0;
5982
5914
  uniforms.softDistance.value = softDistance;
5983
- uniforms.velocityCurveEnabled.value = velocityCurve || curveTexturePath ? 1 : 0;
5984
- uniforms.rotationSpeedCurveEnabled.value = rotationSpeedCurve || curveTexturePath ? 1 : 0;
5985
- uniforms.fadeSizeCurveEnabled.value = fadeSizeCurve || curveTexturePath ? 1 : 0;
5986
- uniforms.fadeOpacityCurveEnabled.value = fadeOpacityCurve || curveTexturePath ? 1 : 0;
5915
+ uniforms.velocityCurveEnabled.value = curveTextureVelocityEnabled ? 1 : 0;
5916
+ uniforms.rotationSpeedCurveEnabled.value = curveTextureRotationSpeedEnabled ? 1 : 0;
5917
+ uniforms.fadeSizeCurveEnabled.value = curveTextureSizeEnabled ? 1 : 0;
5918
+ uniforms.fadeOpacityCurveEnabled.value = curveTextureOpacityEnabled ? 1 : 0;
5987
5919
  uniforms.orientAxisType.value = axisToNumber(orientAxis);
5988
5920
  uniforms.stretchEnabled.value = stretchBySpeed ? 1 : 0;
5989
5921
  uniforms.stretchFactor.value = (_h = stretchBySpeed == null ? void 0 : stretchBySpeed.factor) != null ? _h : 1;
@@ -6028,11 +5960,10 @@ var init_VFXParticles = __esm({
6028
5960
  startPositionAsDirection,
6029
5961
  softParticles,
6030
5962
  softDistance,
6031
- velocityCurve,
6032
- rotationSpeedCurve,
6033
- fadeSizeCurve,
6034
- fadeOpacityCurve,
6035
- curveTexturePath,
5963
+ curveTextureVelocityEnabled,
5964
+ curveTextureRotationSpeedEnabled,
5965
+ curveTextureSizeEnabled,
5966
+ curveTextureOpacityEnabled,
6036
5967
  orientAxis,
6037
5968
  stretchBySpeed
6038
5969
  ]);
@@ -7118,12 +7049,14 @@ export {
7118
7049
  Appearance,
7119
7050
  AttractorType2 as AttractorType,
7120
7051
  Blending,
7052
+ CurveChannel2 as CurveChannel,
7121
7053
  Easing2 as Easing,
7122
7054
  EmitterShape,
7123
7055
  Lighting,
7124
7056
  VFXEmitter,
7125
7057
  VFXParticles,
7126
7058
  bakeCurveToArray,
7059
+ buildCurveTextureBin2 as buildCurveTextureBin,
7127
7060
  createCombinedCurveTexture3 as createCombinedCurveTexture,
7128
7061
  useCurveTextureAsync,
7129
7062
  useVFXEmitter,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "r3f-vfx",
3
- "version": "0.1.2",
3
+ "version": "0.1.4",
4
4
  "license": "MIT",
5
5
  "type": "module",
6
6
  "repository": {
@@ -24,7 +24,7 @@
24
24
  "prepublishOnly": "bun run copy-readme"
25
25
  },
26
26
  "dependencies": {
27
- "core-vfx": "0.0.5",
27
+ "core-vfx": "0.0.7",
28
28
  "zustand": "5.0.10"
29
29
  },
30
30
  "devDependencies": {