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