r3f-vfx 0.1.8 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (4) hide show
  1. package/README.md +471 -471
  2. package/dist/index.d.ts +25 -233
  3. package/dist/index.js +455 -1411
  4. package/package.json +3 -3
package/dist/index.js CHANGED
@@ -34,14 +34,13 @@ var __objRest = (source, exclude) => {
34
34
  import {
35
35
  forwardRef,
36
36
  useImperativeHandle,
37
- useEffect as useEffect2,
38
- useRef as useRef2,
39
- useMemo as useMemo2,
37
+ useEffect,
38
+ useRef,
39
+ useMemo,
40
40
  useCallback,
41
- useState as useState2
41
+ useState
42
42
  } from "react";
43
43
  import { useFrame, useThree } from "@react-three/fiber";
44
- import * as THREE from "three/webgpu";
45
44
 
46
45
  // src/react-store.ts
47
46
  import { coreStore } from "core-vfx";
@@ -56,163 +55,45 @@ var useVFXStore = Object.assign(useVFXStoreImpl, {
56
55
  getInitialState: coreStore.getInitialState
57
56
  });
58
57
 
59
- // src/useCurveTextureAsync.ts
60
- import { useRef, useEffect, useMemo, useState } from "react";
61
- import {
62
- createDefaultCurveTexture,
63
- createCombinedCurveTexture,
64
- loadCurveTextureFromPath,
65
- CurveChannel
66
- } from "core-vfx";
67
- var useCurveTextureAsync = (sizeCurve, opacityCurve, velocityCurve, rotationSpeedCurve, curveTexturePath = null) => {
68
- const hasAnyCurve = sizeCurve || opacityCurve || velocityCurve || rotationSpeedCurve;
69
- const texture = useMemo(() => {
70
- if (!curveTexturePath && hasAnyCurve) {
71
- return createCombinedCurveTexture(
72
- sizeCurve,
73
- opacityCurve,
74
- velocityCurve,
75
- rotationSpeedCurve
76
- );
77
- }
78
- return createDefaultCurveTexture();
79
- }, [
80
- sizeCurve,
81
- opacityCurve,
82
- velocityCurve,
83
- rotationSpeedCurve,
84
- curveTexturePath,
85
- hasAnyCurve
86
- ]);
87
- const [loadedTexture, setLoadedTexture] = useState(
88
- null
89
- );
90
- const [loadedChannels, setLoadedChannels] = useState(0);
91
- useEffect(() => {
92
- if (!curveTexturePath) {
93
- setLoadedTexture(null);
94
- setLoadedChannels(0);
95
- return;
96
- }
97
- let cancelled = false;
98
- loadCurveTextureFromPath(curveTexturePath).then((result) => {
99
- if (!cancelled) {
100
- setLoadedTexture(result.texture);
101
- setLoadedChannels(result.activeChannels);
102
- } else {
103
- result.texture.dispose();
104
- }
105
- }).catch((err) => {
106
- console.warn(
107
- `Failed to load curve texture: ${curveTexturePath}, falling back to baking`,
108
- err
109
- );
110
- if (!cancelled && hasAnyCurve) {
111
- setLoadedTexture(
112
- createCombinedCurveTexture(
113
- sizeCurve,
114
- opacityCurve,
115
- velocityCurve,
116
- rotationSpeedCurve
117
- )
118
- );
119
- let mask = 0;
120
- if (sizeCurve) mask |= CurveChannel.SIZE;
121
- if (opacityCurve) mask |= CurveChannel.OPACITY;
122
- if (velocityCurve) mask |= CurveChannel.VELOCITY;
123
- if (rotationSpeedCurve) mask |= CurveChannel.ROTATION_SPEED;
124
- setLoadedChannels(mask);
125
- }
126
- });
127
- return () => {
128
- cancelled = true;
129
- };
130
- }, [
131
- curveTexturePath,
132
- sizeCurve,
133
- opacityCurve,
134
- velocityCurve,
135
- rotationSpeedCurve,
136
- hasAnyCurve
137
- ]);
138
- const activeTexture = loadedTexture != null ? loadedTexture : texture;
139
- const sizeEnabled = loadedTexture ? !!(loadedChannels & CurveChannel.SIZE) : !!sizeCurve;
140
- const opacityEnabled = loadedTexture ? !!(loadedChannels & CurveChannel.OPACITY) : !!opacityCurve;
141
- const velocityEnabled = loadedTexture ? !!(loadedChannels & CurveChannel.VELOCITY) : !!velocityCurve;
142
- const rotationSpeedEnabled = loadedTexture ? !!(loadedChannels & CurveChannel.ROTATION_SPEED) : !!rotationSpeedCurve;
143
- const prevTextureRef = useRef(null);
144
- useEffect(() => {
145
- const prev = prevTextureRef.current;
146
- if (prev && prev !== activeTexture) {
147
- prev.dispose();
148
- }
149
- prevTextureRef.current = activeTexture;
150
- }, [activeTexture]);
151
- useEffect(() => {
152
- return () => {
153
- var _a;
154
- (_a = prevTextureRef.current) == null ? void 0 : _a.dispose();
155
- prevTextureRef.current = null;
156
- };
157
- }, []);
158
- return {
159
- texture: activeTexture,
160
- sizeEnabled,
161
- opacityEnabled,
162
- velocityEnabled,
163
- rotationSpeedEnabled
164
- };
165
- };
166
-
167
58
  // src/VFXParticles.tsx
168
- import { uniform, instancedArray } from "three/tsl";
169
59
  import {
170
60
  Appearance,
171
61
  Blending,
172
62
  EmitterShape,
173
63
  Lighting,
174
- MAX_ATTRACTORS,
175
- hexToRgb,
176
- toRange,
177
- easingToType,
178
- axisToNumber,
179
- toRotation3D,
180
- lifetimeToFadeRate,
181
- createInitCompute,
182
- createSpawnCompute,
183
- createUpdateCompute,
184
- createParticleMaterial
64
+ VFXParticleSystem,
65
+ isWebGPUBackend,
66
+ isNonDefaultRotation,
67
+ normalizeProps,
68
+ updateUniforms,
69
+ updateUniformsPartial,
70
+ resolveFeatures
185
71
  } from "core-vfx";
186
72
  import {
187
73
  Appearance as Appearance2,
188
74
  Blending as Blending2,
189
75
  EmitterShape as EmitterShape2,
190
- AttractorType as AttractorType2,
191
- Easing as Easing2,
76
+ AttractorType,
77
+ Easing,
192
78
  Lighting as Lighting2,
193
79
  bakeCurveToArray,
194
- createCombinedCurveTexture as createCombinedCurveTexture3,
80
+ createCombinedCurveTexture,
195
81
  buildCurveTextureBin,
196
- CurveChannel as CurveChannel2
82
+ CurveChannel
197
83
  } from "core-vfx";
198
- import { jsx } from "react/jsx-runtime";
199
- var VFXParticles = forwardRef(
200
- function VFXParticles2({
84
+ import { Fragment, jsx } from "react/jsx-runtime";
85
+ var VFXParticlesImpl = forwardRef(
86
+ function VFXParticlesImpl2({
201
87
  name,
202
- // Optional name for registering with useVFXStore (enables VFXEmitter linking)
203
88
  maxParticles = 1e4,
204
89
  size = [0.1, 0.3],
205
90
  colorStart = ["#ffffff"],
206
91
  colorEnd = null,
207
- // If null, uses colorStart (no color transition)
208
92
  fadeSize = [1, 0],
209
93
  fadeSizeCurve = null,
210
- // Curve data { points: [...] } - controls fadeSize over lifetime (overrides fadeSize if set)
211
94
  fadeOpacity = [1, 0],
212
95
  fadeOpacityCurve = null,
213
- // Curve data { points: [...] } - controls fadeOpacity over lifetime (overrides fadeOpacity if set)
214
96
  velocityCurve = null,
215
- // Curve data { points: [...] } - controls velocity/speed over lifetime (overrides friction if set)
216
97
  gravity = [0, 0, 0],
217
98
  lifetime = [1, 2],
218
99
  direction = [
@@ -220,144 +101,81 @@ var VFXParticles = forwardRef(
220
101
  [0, 1],
221
102
  [-1, 1]
222
103
  ],
223
- // [[minX, maxX], [minY, maxY], [minZ, maxZ]] or [min, max] for all axes
224
104
  startPosition = [
225
105
  [0, 0],
226
106
  [0, 0],
227
107
  [0, 0]
228
108
  ],
229
- // [[minX, maxX], [minY, maxY], [minZ, maxZ]] offset from spawn position
230
109
  speed = [0.1, 0.1],
231
110
  friction = { intensity: 0, easing: "linear" },
232
- // { intensity: [start, end] or single value, easing: string }
233
- // intensity: 1 = max friction (almost stopped), 0 = no friction (normal), negative = boost/acceleration
234
111
  appearance = Appearance.GRADIENT,
235
112
  alphaMap = null,
236
113
  flipbook = null,
237
- // { rows: 4, columns: 8 }
238
114
  rotation = [0, 0],
239
- // [min, max] in radians
240
115
  rotationSpeed = [0, 0],
241
- // [min, max] rotation speed in radians/second
242
116
  rotationSpeedCurve = null,
243
- // Curve data { points: [...] } - controls rotation speed over lifetime
244
117
  geometry = null,
245
- // Custom geometry (e.g. new THREE.SphereGeometry(0.5, 8, 8))
246
118
  orientToDirection = false,
247
- // Rotate geometry to face velocity direction (geometry mode only)
248
119
  orientAxis = "z",
249
- // Which local axis aligns with velocity: "x", "y", "z", "-x", "-y", "-z"
250
120
  stretchBySpeed = null,
251
- // { factor: 2, maxStretch: 5 } - stretch particles in velocity direction based on effective speed
252
121
  lighting = Lighting.STANDARD,
253
- // 'basic' | 'standard' | 'physical' - material type for geometry mode
254
122
  shadow = false,
255
- // Enable both castShadow and receiveShadow on geometry instances
256
123
  blending = Blending.NORMAL,
257
124
  intensity = 1,
258
125
  position = [0, 0, 0],
259
126
  autoStart = true,
260
127
  delay = 0,
261
128
  backdropNode = null,
262
- // TSL node or function for backdrop sampling
263
129
  opacityNode = null,
264
- // TSL node or function for custom opacity control
265
130
  colorNode = null,
266
- // TSL node or function to override color (receives particleData, should return vec4)
267
131
  alphaTestNode = null,
268
- // TSL node or function for custom alpha test/discard (return true to discard fragment)
269
132
  castShadowNode = null,
270
- // TSL node or function for shadow map output (what shadow the particle casts)
271
133
  emitCount = 1,
272
- // Emitter shape props
273
134
  emitterShape = EmitterShape.BOX,
274
- // Emission shape type
275
135
  emitterRadius = [0, 1],
276
- // [inner, outer] radius for sphere/cone/disk (inner=0 for solid)
277
136
  emitterAngle = Math.PI / 4,
278
- // Cone angle in radians (0 = line, PI/2 = hemisphere)
279
137
  emitterHeight = [0, 1],
280
- // [min, max] height for cone
281
138
  emitterSurfaceOnly = false,
282
- // Emit from surface only (sphere/disk)
283
139
  emitterDirection = [0, 1, 0],
284
- // Direction for cone/disk normal
285
- // Turbulence (curl noise)
286
140
  turbulence = null,
287
- // { intensity: 0.5, frequency: 1, speed: 1 }
288
- // Attractors - array of up to 4 attractors
289
- // { position: [x,y,z], strength: 1, radius: 3, type: 'point'|'vortex', axis?: [x,y,z] }
290
141
  attractors = null,
291
- // Simple attract to center - particles move from spawn position to center over lifetime
292
- // Overrides speed/direction - lifetime controls how long it takes to reach center
293
142
  attractToCenter = false,
294
- // Use start position offset as direction - particles move in the direction of their spawn offset
295
143
  startPositionAsDirection = false,
296
- // Soft particles - fade when intersecting scene geometry
297
144
  softParticles = false,
298
145
  softDistance = 0.5,
299
- // Distance in world units over which to fade
300
- // Plane collision - particles bounce or die when hitting a plane
301
- // { plane: { y: 0 }, bounce: 0.3, friction: 0.8, die: false, sizeBasedGravity: 0 }
302
146
  collision = null,
303
- // Debug mode - shows tweakable control panel
304
147
  debug = false,
305
- // Path to pre-baked curve texture (skips runtime baking for faster load)
306
148
  curveTexturePath = null,
307
- // Depth test
308
149
  depthTest = true,
309
- // Render order
310
150
  renderOrder = 0
311
151
  }, ref) {
312
152
  var _a;
313
153
  const { gl: renderer } = useThree();
314
- const spriteRef = useRef2(null);
315
- const initialized = useRef2(false);
316
- const nextIndex = useRef2(0);
317
- const [emitting, setEmitting] = useState2(autoStart);
318
- const emitAccumulator = useRef2(0);
319
- const delayRef = useRef2(delay);
320
- const emitCountRef = useRef2(emitCount);
321
- const turbulenceRef = useRef2(turbulence);
322
- const [activeMaxParticles, setActiveMaxParticles] = useState2(maxParticles);
323
- const [activeLighting, setActiveLighting] = useState2(lighting);
324
- const [activeAppearance, setActiveAppearance] = useState2(appearance);
325
- const [activeOrientToDirection, setActiveOrientToDirection] = useState2(orientToDirection);
326
- const [activeGeometry, setActiveGeometry] = useState2(geometry);
327
- const [activeShadow, setActiveShadow] = useState2(shadow);
328
- const [activeFadeSizeCurve, setActiveFadeSizeCurve] = useState2(fadeSizeCurve);
329
- const [activeFadeOpacityCurve, setActiveFadeOpacityCurve] = useState2(fadeOpacityCurve);
330
- const [activeVelocityCurve, setActiveVelocityCurve] = useState2(velocityCurve);
331
- const [activeRotationSpeedCurve, setActiveRotationSpeedCurve] = useState2(rotationSpeedCurve);
332
- const [activeTurbulence, setActiveTurbulence] = useState2(
154
+ const spriteRef = useRef(null);
155
+ const [emitting, setEmitting] = useState(autoStart);
156
+ const debugValuesRef = useRef(null);
157
+ const [activeMaxParticles, setActiveMaxParticles] = useState(maxParticles);
158
+ const [activeLighting, setActiveLighting] = useState(lighting);
159
+ const [activeAppearance, setActiveAppearance] = useState(appearance);
160
+ const [activeOrientToDirection, setActiveOrientToDirection] = useState(orientToDirection);
161
+ const [activeGeometry, setActiveGeometry] = useState(geometry);
162
+ const [activeShadow, setActiveShadow] = useState(shadow);
163
+ const [activeFadeSizeCurve, setActiveFadeSizeCurve] = useState(fadeSizeCurve);
164
+ const [activeFadeOpacityCurve, setActiveFadeOpacityCurve] = useState(fadeOpacityCurve);
165
+ const [activeVelocityCurve, setActiveVelocityCurve] = useState(velocityCurve);
166
+ const [activeRotationSpeedCurve, setActiveRotationSpeedCurve] = useState(rotationSpeedCurve);
167
+ const [activeTurbulence, setActiveTurbulence] = useState(
333
168
  turbulence !== null && ((_a = turbulence == null ? void 0 : turbulence.intensity) != null ? _a : 0) > 0
334
169
  );
335
- const [activeAttractors, setActiveAttractors] = useState2(
170
+ const [activeAttractors, setActiveAttractors] = useState(
336
171
  attractors !== null && attractors.length > 0
337
172
  );
338
- const [activeCollision, setActiveCollision] = useState2(collision !== null);
339
- const [activeNeedsPerParticleColor, setActiveNeedsPerParticleColor] = useState2(colorStart.length > 1 || colorEnd !== null);
340
- const isNonDefaultRotation = (r) => {
341
- if (typeof r === "number") return r !== 0;
342
- if (Array.isArray(r) && r.length === 2 && typeof r[0] === "number") {
343
- return r[0] !== 0 || r[1] !== 0;
344
- }
345
- if (Array.isArray(r)) {
346
- return r.some(
347
- (axis) => Array.isArray(axis) && (axis[0] !== 0 || axis[1] !== 0)
348
- );
349
- }
350
- return false;
351
- };
352
- const [activeNeedsRotation, setActiveNeedsRotation] = useState2(
173
+ const [activeCollision, setActiveCollision] = useState(collision !== null);
174
+ const [activeNeedsPerParticleColor, setActiveNeedsPerParticleColor] = useState(colorStart.length > 1 || colorEnd !== null);
175
+ const [activeNeedsRotation, setActiveNeedsRotation] = useState(
353
176
  isNonDefaultRotation(rotation) || isNonDefaultRotation(rotationSpeed)
354
177
  );
355
- useEffect2(() => {
356
- delayRef.current = delay;
357
- emitCountRef.current = emitCount;
358
- turbulenceRef.current = turbulence;
359
- }, [delay, emitCount, turbulence]);
360
- useEffect2(() => {
178
+ useEffect(() => {
361
179
  var _a2;
362
180
  if (!debug) {
363
181
  setActiveMaxParticles(maxParticles);
@@ -402,699 +220,221 @@ var VFXParticles = forwardRef(
402
220
  attractors,
403
221
  collision
404
222
  ]);
405
- const sizeRange = useMemo2(() => toRange(size, [0.1, 0.3]), [size]);
406
- const speedRange = useMemo2(() => toRange(speed, [0.1, 0.1]), [speed]);
407
- const fadeSizeRange = useMemo2(() => toRange(fadeSize, [1, 0]), [fadeSize]);
408
- const fadeOpacityRange = useMemo2(
409
- () => toRange(fadeOpacity, [1, 0]),
410
- [fadeOpacity]
411
- );
412
- const {
413
- texture: curveTexture,
414
- sizeEnabled: curveTextureSizeEnabled,
415
- opacityEnabled: curveTextureOpacityEnabled,
416
- velocityEnabled: curveTextureVelocityEnabled,
417
- rotationSpeedEnabled: curveTextureRotationSpeedEnabled
418
- } = useCurveTextureAsync(
419
- activeFadeSizeCurve,
420
- activeFadeOpacityCurve,
421
- activeVelocityCurve,
422
- activeRotationSpeedCurve,
423
- curveTexturePath
424
- );
425
- const lifetimeRange = useMemo2(() => toRange(lifetime, [1, 2]), [lifetime]);
426
- const rotation3D = useMemo2(() => toRotation3D(rotation), [rotation]);
427
- const rotationSpeed3D = useMemo2(
428
- () => toRotation3D(rotationSpeed),
429
- [rotationSpeed]
430
- );
431
- const direction3D = useMemo2(() => toRotation3D(direction), [direction]);
432
- const startPosition3D = useMemo2(
433
- () => toRotation3D(startPosition),
434
- [startPosition]
435
- );
436
- const emitterRadiusRange = useMemo2(
437
- () => toRange(emitterRadius, [0, 1]),
438
- [emitterRadius]
439
- );
440
- const emitterHeightRange = useMemo2(
441
- () => toRange(emitterHeight, [0, 1]),
442
- [emitterHeight]
443
- );
444
- const activeFeatures = useMemo2(
445
- () => ({
446
- // Storage array features
447
- needsPerParticleColor: activeNeedsPerParticleColor,
448
- needsRotation: activeNeedsRotation,
449
- // Shader features (skip code entirely when disabled)
450
- turbulence: activeTurbulence,
451
- attractors: activeAttractors,
452
- collision: activeCollision,
453
- rotation: activeNeedsRotation,
454
- perParticleColor: activeNeedsPerParticleColor
455
- }),
223
+ const system = useMemo(
224
+ () => {
225
+ var _a2, _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;
226
+ const dbg = debug ? debugValuesRef.current : null;
227
+ return new VFXParticleSystem(
228
+ renderer,
229
+ {
230
+ maxParticles: activeMaxParticles,
231
+ size: (_a2 = dbg == null ? void 0 : dbg.size) != null ? _a2 : size,
232
+ colorStart: (_b = dbg == null ? void 0 : dbg.colorStart) != null ? _b : colorStart,
233
+ colorEnd: (dbg == null ? void 0 : dbg.colorEnd) !== void 0 ? dbg.colorEnd : colorEnd,
234
+ fadeSize: (_c = dbg == null ? void 0 : dbg.fadeSize) != null ? _c : fadeSize,
235
+ fadeSizeCurve: activeFadeSizeCurve,
236
+ fadeOpacity: (_d = dbg == null ? void 0 : dbg.fadeOpacity) != null ? _d : fadeOpacity,
237
+ fadeOpacityCurve: activeFadeOpacityCurve,
238
+ velocityCurve: activeVelocityCurve,
239
+ gravity: (_e = dbg == null ? void 0 : dbg.gravity) != null ? _e : gravity,
240
+ lifetime: (_f = dbg == null ? void 0 : dbg.lifetime) != null ? _f : lifetime,
241
+ direction: (_g = dbg == null ? void 0 : dbg.direction) != null ? _g : direction,
242
+ startPosition: (_h = dbg == null ? void 0 : dbg.startPosition) != null ? _h : startPosition,
243
+ speed: (_i = dbg == null ? void 0 : dbg.speed) != null ? _i : speed,
244
+ friction: (_j = dbg == null ? void 0 : dbg.friction) != null ? _j : friction,
245
+ appearance: activeAppearance,
246
+ alphaMap,
247
+ flipbook,
248
+ rotation: (_k = dbg == null ? void 0 : dbg.rotation) != null ? _k : rotation,
249
+ rotationSpeed: (_l = dbg == null ? void 0 : dbg.rotationSpeed) != null ? _l : rotationSpeed,
250
+ rotationSpeedCurve: activeRotationSpeedCurve,
251
+ geometry: activeGeometry,
252
+ orientToDirection: activeOrientToDirection,
253
+ orientAxis: (_m = dbg == null ? void 0 : dbg.orientAxis) != null ? _m : orientAxis,
254
+ stretchBySpeed: (_n = dbg == null ? void 0 : dbg.stretchBySpeed) != null ? _n : stretchBySpeed,
255
+ lighting: activeLighting,
256
+ shadow: activeShadow,
257
+ blending: (_o = dbg == null ? void 0 : dbg.blending) != null ? _o : blending,
258
+ intensity: (_p = dbg == null ? void 0 : dbg.intensity) != null ? _p : intensity,
259
+ position: (_q = dbg == null ? void 0 : dbg.position) != null ? _q : position,
260
+ autoStart: (_r = dbg == null ? void 0 : dbg.autoStart) != null ? _r : autoStart,
261
+ delay: (_s = dbg == null ? void 0 : dbg.delay) != null ? _s : delay,
262
+ emitCount: (_t = dbg == null ? void 0 : dbg.emitCount) != null ? _t : emitCount,
263
+ emitterShape: (_u = dbg == null ? void 0 : dbg.emitterShape) != null ? _u : emitterShape,
264
+ emitterRadius: (_v = dbg == null ? void 0 : dbg.emitterRadius) != null ? _v : emitterRadius,
265
+ emitterAngle: (_w = dbg == null ? void 0 : dbg.emitterAngle) != null ? _w : emitterAngle,
266
+ emitterHeight: (_x = dbg == null ? void 0 : dbg.emitterHeight) != null ? _x : emitterHeight,
267
+ emitterSurfaceOnly: (_y = dbg == null ? void 0 : dbg.emitterSurfaceOnly) != null ? _y : emitterSurfaceOnly,
268
+ emitterDirection: (_z = dbg == null ? void 0 : dbg.emitterDirection) != null ? _z : emitterDirection,
269
+ turbulence: (_A = dbg == null ? void 0 : dbg.turbulence) != null ? _A : turbulence,
270
+ attractors: (_B = dbg == null ? void 0 : dbg.attractors) != null ? _B : attractors,
271
+ attractToCenter: (_C = dbg == null ? void 0 : dbg.attractToCenter) != null ? _C : attractToCenter,
272
+ startPositionAsDirection: (_D = dbg == null ? void 0 : dbg.startPositionAsDirection) != null ? _D : startPositionAsDirection,
273
+ softParticles: (_E = dbg == null ? void 0 : dbg.softParticles) != null ? _E : softParticles,
274
+ softDistance: (_F = dbg == null ? void 0 : dbg.softDistance) != null ? _F : softDistance,
275
+ collision: (_G = dbg == null ? void 0 : dbg.collision) != null ? _G : collision,
276
+ backdropNode,
277
+ opacityNode,
278
+ colorNode,
279
+ alphaTestNode,
280
+ castShadowNode,
281
+ depthTest: (_H = dbg == null ? void 0 : dbg.depthTest) != null ? _H : depthTest,
282
+ renderOrder: (_I = dbg == null ? void 0 : dbg.renderOrder) != null ? _I : renderOrder,
283
+ curveTexturePath
284
+ }
285
+ );
286
+ },
287
+ // Only recreate when structural props change (features, maxParticles, etc.)
288
+ // eslint-disable-next-line react-hooks/exhaustive-deps
456
289
  [
290
+ renderer,
291
+ activeMaxParticles,
292
+ activeLighting,
293
+ activeAppearance,
294
+ activeOrientToDirection,
295
+ activeGeometry,
296
+ activeShadow,
457
297
  activeNeedsPerParticleColor,
458
298
  activeNeedsRotation,
459
299
  activeTurbulence,
460
300
  activeAttractors,
461
- activeCollision
301
+ activeCollision,
302
+ activeFadeSizeCurve,
303
+ activeFadeOpacityCurve,
304
+ activeVelocityCurve,
305
+ activeRotationSpeedCurve,
306
+ alphaMap,
307
+ flipbook,
308
+ blending,
309
+ backdropNode,
310
+ opacityNode,
311
+ colorNode,
312
+ alphaTestNode,
313
+ castShadowNode,
314
+ softParticles,
315
+ curveTexturePath
462
316
  ]
463
317
  );
464
- const frictionIntensityRange = useMemo2(() => {
465
- if (typeof friction === "object" && friction !== null && "intensity" in friction) {
466
- return toRange(friction.intensity, [0, 0]);
467
- }
468
- return [0, 0];
469
- }, [friction]);
470
- const frictionEasingType = useMemo2(() => {
318
+ useEffect(() => {
319
+ system.init();
320
+ }, [system]);
321
+ useEffect(() => {
471
322
  var _a2;
472
- if (typeof friction === "object" && friction !== null && "easing" in friction) {
473
- return easingToType((_a2 = friction.easing) != null ? _a2 : "linear");
474
- }
475
- return 0;
476
- }, [friction]);
477
- const startColors = useMemo2(() => {
478
- const colors = colorStart.slice(0, 8).map(hexToRgb);
479
- while (colors.length < 8)
480
- colors.push(colors[colors.length - 1] || [1, 1, 1]);
481
- return colors;
482
- }, [colorStart]);
483
- const effectiveColorEnd = colorEnd != null ? colorEnd : colorStart;
484
- const endColors = useMemo2(() => {
485
- const colors = effectiveColorEnd.slice(0, 8).map(hexToRgb);
486
- while (colors.length < 8)
487
- colors.push(colors[colors.length - 1] || [1, 1, 1]);
488
- return colors;
489
- }, [effectiveColorEnd]);
490
- const uniforms = useMemo2(
491
- () => {
492
- var _a2, _b, _c, _d, _e, _f, _g, _h, _i, _j;
493
- return {
494
- sizeMin: uniform(sizeRange[0]),
495
- sizeMax: uniform(sizeRange[1]),
496
- fadeSizeStart: uniform(fadeSizeRange[0]),
497
- fadeSizeEnd: uniform(fadeSizeRange[1]),
498
- fadeOpacityStart: uniform(fadeOpacityRange[0]),
499
- fadeOpacityEnd: uniform(fadeOpacityRange[1]),
500
- gravity: uniform(new THREE.Vector3(...gravity)),
501
- frictionIntensityStart: uniform(frictionIntensityRange[0]),
502
- frictionIntensityEnd: uniform(frictionIntensityRange[1]),
503
- frictionEasingType: uniform(frictionEasingType),
504
- speedMin: uniform(speedRange[0]),
505
- speedMax: uniform(speedRange[1]),
506
- lifetimeMin: uniform(lifetimeToFadeRate(lifetimeRange[1])),
507
- lifetimeMax: uniform(lifetimeToFadeRate(lifetimeRange[0])),
508
- deltaTime: uniform(0.016),
509
- // Will be updated each frame
510
- // 3D direction ranges
511
- dirMinX: uniform(direction3D[0][0]),
512
- dirMaxX: uniform(direction3D[0][1]),
513
- dirMinY: uniform(direction3D[1][0]),
514
- dirMaxY: uniform(direction3D[1][1]),
515
- dirMinZ: uniform(direction3D[2][0]),
516
- dirMaxZ: uniform(direction3D[2][1]),
517
- // 3D start position offset ranges
518
- startPosMinX: uniform(startPosition3D[0][0]),
519
- startPosMaxX: uniform(startPosition3D[0][1]),
520
- startPosMinY: uniform(startPosition3D[1][0]),
521
- startPosMaxY: uniform(startPosition3D[1][1]),
522
- startPosMinZ: uniform(startPosition3D[2][0]),
523
- startPosMaxZ: uniform(startPosition3D[2][1]),
524
- spawnPosition: uniform(new THREE.Vector3(...position)),
525
- spawnIndexStart: uniform(0),
526
- spawnIndexEnd: uniform(0),
527
- spawnSeed: uniform(0),
528
- intensity: uniform(intensity),
529
- // 3D rotation ranges
530
- rotationMinX: uniform(rotation3D[0][0]),
531
- rotationMaxX: uniform(rotation3D[0][1]),
532
- rotationMinY: uniform(rotation3D[1][0]),
533
- rotationMaxY: uniform(rotation3D[1][1]),
534
- rotationMinZ: uniform(rotation3D[2][0]),
535
- rotationMaxZ: uniform(rotation3D[2][1]),
536
- // 3D rotation speed ranges (radians/second)
537
- rotationSpeedMinX: uniform(rotationSpeed3D[0][0]),
538
- rotationSpeedMaxX: uniform(rotationSpeed3D[0][1]),
539
- rotationSpeedMinY: uniform(rotationSpeed3D[1][0]),
540
- rotationSpeedMaxY: uniform(rotationSpeed3D[1][1]),
541
- rotationSpeedMinZ: uniform(rotationSpeed3D[2][0]),
542
- rotationSpeedMaxZ: uniform(rotationSpeed3D[2][1]),
543
- // Color arrays (8 colors max each)
544
- colorStartCount: uniform(colorStart.length),
545
- colorEndCount: uniform(effectiveColorEnd.length),
546
- colorStart0: uniform(new THREE.Color(...startColors[0])),
547
- colorStart1: uniform(new THREE.Color(...startColors[1])),
548
- colorStart2: uniform(new THREE.Color(...startColors[2])),
549
- colorStart3: uniform(new THREE.Color(...startColors[3])),
550
- colorStart4: uniform(new THREE.Color(...startColors[4])),
551
- colorStart5: uniform(new THREE.Color(...startColors[5])),
552
- colorStart6: uniform(new THREE.Color(...startColors[6])),
553
- colorStart7: uniform(new THREE.Color(...startColors[7])),
554
- colorEnd0: uniform(new THREE.Color(...endColors[0])),
555
- colorEnd1: uniform(new THREE.Color(...endColors[1])),
556
- colorEnd2: uniform(new THREE.Color(...endColors[2])),
557
- colorEnd3: uniform(new THREE.Color(...endColors[3])),
558
- colorEnd4: uniform(new THREE.Color(...endColors[4])),
559
- colorEnd5: uniform(new THREE.Color(...endColors[5])),
560
- colorEnd6: uniform(new THREE.Color(...endColors[6])),
561
- colorEnd7: uniform(new THREE.Color(...endColors[7])),
562
- // Emitter shape uniforms
563
- emitterShapeType: uniform(emitterShape),
564
- emitterRadiusInner: uniform(emitterRadiusRange[0]),
565
- emitterRadiusOuter: uniform(emitterRadiusRange[1]),
566
- emitterAngle: uniform(emitterAngle),
567
- emitterHeightMin: uniform(emitterHeightRange[0]),
568
- emitterHeightMax: uniform(emitterHeightRange[1]),
569
- emitterSurfaceOnly: uniform(emitterSurfaceOnly ? 1 : 0),
570
- emitterDir: uniform(new THREE.Vector3(...emitterDirection).normalize()),
571
- // Turbulence uniforms
572
- turbulenceIntensity: uniform((_a2 = turbulence == null ? void 0 : turbulence.intensity) != null ? _a2 : 0),
573
- turbulenceFrequency: uniform((_b = turbulence == null ? void 0 : turbulence.frequency) != null ? _b : 1),
574
- turbulenceSpeed: uniform((_c = turbulence == null ? void 0 : turbulence.speed) != null ? _c : 1),
575
- turbulenceTime: uniform(0),
576
- // Updated each frame
577
- // Attractor uniforms (up to 4)
578
- attractorCount: uniform(0),
579
- attractor0Pos: uniform(new THREE.Vector3(0, 0, 0)),
580
- attractor0Strength: uniform(0),
581
- attractor0Radius: uniform(1),
582
- attractor0Type: uniform(0),
583
- attractor0Axis: uniform(new THREE.Vector3(0, 1, 0)),
584
- attractor1Pos: uniform(new THREE.Vector3(0, 0, 0)),
585
- attractor1Strength: uniform(0),
586
- attractor1Radius: uniform(1),
587
- attractor1Type: uniform(0),
588
- attractor1Axis: uniform(new THREE.Vector3(0, 1, 0)),
589
- attractor2Pos: uniform(new THREE.Vector3(0, 0, 0)),
590
- attractor2Strength: uniform(0),
591
- attractor2Radius: uniform(1),
592
- attractor2Type: uniform(0),
593
- attractor2Axis: uniform(new THREE.Vector3(0, 1, 0)),
594
- attractor3Pos: uniform(new THREE.Vector3(0, 0, 0)),
595
- attractor3Strength: uniform(0),
596
- attractor3Radius: uniform(1),
597
- attractor3Type: uniform(0),
598
- attractor3Axis: uniform(new THREE.Vector3(0, 1, 0)),
599
- // Simple attract to center
600
- attractToCenter: uniform(attractToCenter ? 1 : 0),
601
- // Use start position as direction
602
- startPositionAsDirection: uniform(startPositionAsDirection ? 1 : 0),
603
- // Soft particles
604
- softParticlesEnabled: uniform(softParticles ? 1 : 0),
605
- softDistance: uniform(softDistance),
606
- // Curve enabled flags (set by useCurveTextureAsync per-channel info)
607
- velocityCurveEnabled: uniform(0),
608
- rotationSpeedCurveEnabled: uniform(0),
609
- fadeSizeCurveEnabled: uniform(0),
610
- fadeOpacityCurveEnabled: uniform(0),
611
- // Orient axis: 0=+X, 1=+Y, 2=+Z, 3=-X, 4=-Y, 5=-Z
612
- orientAxisType: uniform(axisToNumber(orientAxis)),
613
- // Stretch by speed (uses effective velocity after curve modifier)
614
- stretchEnabled: uniform(stretchBySpeed ? 1 : 0),
615
- stretchFactor: uniform((_d = stretchBySpeed == null ? void 0 : stretchBySpeed.factor) != null ? _d : 1),
616
- stretchMax: uniform((_e = stretchBySpeed == null ? void 0 : stretchBySpeed.maxStretch) != null ? _e : 5),
617
- // Collision uniforms
618
- collisionEnabled: uniform(collision ? 1 : 0),
619
- collisionPlaneY: uniform((_g = (_f = collision == null ? void 0 : collision.plane) == null ? void 0 : _f.y) != null ? _g : 0),
620
- collisionBounce: uniform((_h = collision == null ? void 0 : collision.bounce) != null ? _h : 0.3),
621
- collisionFriction: uniform((_i = collision == null ? void 0 : collision.friction) != null ? _i : 0.8),
622
- collisionDie: uniform((collision == null ? void 0 : collision.die) ? 1 : 0),
623
- // Size-based gravity (inside collision object)
624
- sizeBasedGravity: uniform((_j = collision == null ? void 0 : collision.sizeBasedGravity) != null ? _j : 0)
625
- };
626
- },
627
- []
628
- );
629
- const positionRef = useRef2(position);
630
- useEffect2(() => {
631
- var _a2, _b, _c, _d, _e, _f, _g, _h, _i, _j, _k, _l, _m, _n;
632
323
  if (debug) return;
633
- positionRef.current = position;
634
- uniforms.sizeMin.value = sizeRange[0];
635
- uniforms.sizeMax.value = sizeRange[1];
636
- uniforms.fadeSizeStart.value = fadeSizeRange[0];
637
- uniforms.fadeSizeEnd.value = fadeSizeRange[1];
638
- uniforms.fadeOpacityStart.value = fadeOpacityRange[0];
639
- uniforms.fadeOpacityEnd.value = fadeOpacityRange[1];
640
- uniforms.gravity.value.set(...gravity);
641
- uniforms.frictionIntensityStart.value = frictionIntensityRange[0];
642
- uniforms.frictionIntensityEnd.value = frictionIntensityRange[1];
643
- uniforms.frictionEasingType.value = frictionEasingType;
644
- uniforms.speedMin.value = speedRange[0];
645
- uniforms.speedMax.value = speedRange[1];
646
- uniforms.lifetimeMin.value = lifetimeToFadeRate(lifetimeRange[1]);
647
- uniforms.lifetimeMax.value = lifetimeToFadeRate(lifetimeRange[0]);
648
- uniforms.dirMinX.value = direction3D[0][0];
649
- uniforms.dirMaxX.value = direction3D[0][1];
650
- uniforms.dirMinY.value = direction3D[1][0];
651
- uniforms.dirMaxY.value = direction3D[1][1];
652
- uniforms.dirMinZ.value = direction3D[2][0];
653
- uniforms.dirMaxZ.value = direction3D[2][1];
654
- uniforms.startPosMinX.value = startPosition3D[0][0];
655
- uniforms.startPosMaxX.value = startPosition3D[0][1];
656
- uniforms.startPosMinY.value = startPosition3D[1][0];
657
- uniforms.startPosMaxY.value = startPosition3D[1][1];
658
- uniforms.startPosMinZ.value = startPosition3D[2][0];
659
- uniforms.startPosMaxZ.value = startPosition3D[2][1];
660
- uniforms.rotationMinX.value = rotation3D[0][0];
661
- uniforms.rotationMaxX.value = rotation3D[0][1];
662
- uniforms.rotationMinY.value = rotation3D[1][0];
663
- uniforms.rotationMaxY.value = rotation3D[1][1];
664
- uniforms.rotationMinZ.value = rotation3D[2][0];
665
- uniforms.rotationMaxZ.value = rotation3D[2][1];
666
- uniforms.rotationSpeedMinX.value = rotationSpeed3D[0][0];
667
- uniforms.rotationSpeedMaxX.value = rotationSpeed3D[0][1];
668
- uniforms.rotationSpeedMinY.value = rotationSpeed3D[1][0];
669
- uniforms.rotationSpeedMaxY.value = rotationSpeed3D[1][1];
670
- uniforms.rotationSpeedMinZ.value = rotationSpeed3D[2][0];
671
- uniforms.rotationSpeedMaxZ.value = rotationSpeed3D[2][1];
672
- uniforms.intensity.value = intensity;
673
- uniforms.colorStartCount.value = colorStart.length;
674
- uniforms.colorEndCount.value = effectiveColorEnd.length;
675
- startColors.forEach((c, i) => {
676
- var _a3;
677
- ;
678
- (_a3 = uniforms[`colorStart${i}`]) == null ? void 0 : _a3.value.setRGB(...c);
679
- });
680
- endColors.forEach((c, i) => {
681
- var _a3;
682
- ;
683
- (_a3 = uniforms[`colorEnd${i}`]) == null ? void 0 : _a3.value.setRGB(...c);
324
+ system.setPosition(position);
325
+ system.setDelay(delay);
326
+ system.setEmitCount(emitCount);
327
+ system.setTurbulenceSpeed((_a2 = turbulence == null ? void 0 : turbulence.speed) != null ? _a2 : 1);
328
+ const normalized = normalizeProps({
329
+ size,
330
+ speed,
331
+ fadeSize,
332
+ fadeOpacity,
333
+ lifetime,
334
+ gravity,
335
+ direction,
336
+ startPosition,
337
+ rotation,
338
+ rotationSpeed,
339
+ friction,
340
+ intensity,
341
+ colorStart,
342
+ colorEnd,
343
+ emitterShape,
344
+ emitterRadius,
345
+ emitterAngle,
346
+ emitterHeight,
347
+ emitterSurfaceOnly,
348
+ emitterDirection,
349
+ turbulence,
350
+ attractors,
351
+ attractToCenter,
352
+ startPositionAsDirection,
353
+ softParticles,
354
+ softDistance,
355
+ collision,
356
+ orientAxis,
357
+ stretchBySpeed
684
358
  });
685
- uniforms.emitterShapeType.value = emitterShape;
686
- uniforms.emitterRadiusInner.value = emitterRadiusRange[0];
687
- uniforms.emitterRadiusOuter.value = emitterRadiusRange[1];
688
- uniforms.emitterAngle.value = emitterAngle;
689
- uniforms.emitterHeightMin.value = emitterHeightRange[0];
690
- uniforms.emitterHeightMax.value = emitterHeightRange[1];
691
- uniforms.emitterSurfaceOnly.value = emitterSurfaceOnly ? 1 : 0;
692
- uniforms.emitterDir.value.set(...emitterDirection).normalize();
693
- uniforms.turbulenceIntensity.value = (_a2 = turbulence == null ? void 0 : turbulence.intensity) != null ? _a2 : 0;
694
- uniforms.turbulenceFrequency.value = (_b = turbulence == null ? void 0 : turbulence.frequency) != null ? _b : 1;
695
- uniforms.turbulenceSpeed.value = (_c = turbulence == null ? void 0 : turbulence.speed) != null ? _c : 1;
696
- const attractorList = attractors != null ? attractors : [];
697
- uniforms.attractorCount.value = Math.min(
698
- attractorList.length,
699
- MAX_ATTRACTORS
700
- );
701
- for (let i = 0; i < MAX_ATTRACTORS; i++) {
702
- const a = attractorList[i];
703
- const u = uniforms;
704
- if (a) {
705
- ;
706
- u[`attractor${i}Pos`].value.set(
707
- ...(_d = a.position) != null ? _d : [0, 0, 0]
708
- );
709
- u[`attractor${i}Strength`].value = (_e = a.strength) != null ? _e : 1;
710
- u[`attractor${i}Radius`].value = (_f = a.radius) != null ? _f : 0;
711
- u[`attractor${i}Type`].value = a.type === "vortex" ? 1 : 0;
712
- u[`attractor${i}Axis`].value.set(...(_g = a.axis) != null ? _g : [0, 1, 0]).normalize();
713
- } else {
714
- u[`attractor${i}Strength`].value = 0;
715
- }
716
- }
717
- uniforms.attractToCenter.value = attractToCenter ? 1 : 0;
718
- uniforms.startPositionAsDirection.value = startPositionAsDirection ? 1 : 0;
719
- uniforms.softParticlesEnabled.value = softParticles ? 1 : 0;
720
- uniforms.softDistance.value = softDistance;
721
- uniforms.velocityCurveEnabled.value = curveTextureVelocityEnabled ? 1 : 0;
722
- uniforms.rotationSpeedCurveEnabled.value = curveTextureRotationSpeedEnabled ? 1 : 0;
723
- uniforms.fadeSizeCurveEnabled.value = curveTextureSizeEnabled ? 1 : 0;
724
- uniforms.fadeOpacityCurveEnabled.value = curveTextureOpacityEnabled ? 1 : 0;
725
- uniforms.orientAxisType.value = axisToNumber(orientAxis);
726
- uniforms.stretchEnabled.value = stretchBySpeed ? 1 : 0;
727
- uniforms.stretchFactor.value = (_h = stretchBySpeed == null ? void 0 : stretchBySpeed.factor) != null ? _h : 1;
728
- uniforms.stretchMax.value = (_i = stretchBySpeed == null ? void 0 : stretchBySpeed.maxStretch) != null ? _i : 5;
729
- uniforms.collisionEnabled.value = collision ? 1 : 0;
730
- uniforms.collisionPlaneY.value = (_k = (_j = collision == null ? void 0 : collision.plane) == null ? void 0 : _j.y) != null ? _k : 0;
731
- uniforms.collisionBounce.value = (_l = collision == null ? void 0 : collision.bounce) != null ? _l : 0.3;
732
- uniforms.collisionFriction.value = (_m = collision == null ? void 0 : collision.friction) != null ? _m : 0.8;
733
- uniforms.collisionDie.value = (collision == null ? void 0 : collision.die) ? 1 : 0;
734
- uniforms.sizeBasedGravity.value = (_n = collision == null ? void 0 : collision.sizeBasedGravity) != null ? _n : 0;
359
+ updateUniforms(system.uniforms, normalized);
735
360
  }, [
736
361
  debug,
362
+ system,
737
363
  position,
738
- sizeRange,
739
- fadeSizeRange,
740
- fadeOpacityRange,
364
+ size,
365
+ fadeSize,
366
+ fadeOpacity,
741
367
  gravity,
742
- frictionIntensityRange,
743
- frictionEasingType,
744
- speedRange,
745
- lifetimeRange,
746
- direction3D,
747
- rotation3D,
748
- rotationSpeed3D,
368
+ friction,
369
+ speed,
370
+ lifetime,
371
+ direction,
372
+ rotation,
373
+ rotationSpeed,
749
374
  intensity,
750
375
  colorStart,
751
- effectiveColorEnd,
752
- startColors,
753
- endColors,
754
- uniforms,
376
+ colorEnd,
755
377
  collision,
756
378
  emitterShape,
757
- emitterRadiusRange,
379
+ emitterRadius,
758
380
  emitterAngle,
759
- emitterHeightRange,
381
+ emitterHeight,
760
382
  emitterSurfaceOnly,
761
383
  emitterDirection,
762
384
  turbulence,
763
- startPosition3D,
385
+ startPosition,
764
386
  attractors,
765
387
  attractToCenter,
766
388
  startPositionAsDirection,
767
389
  softParticles,
768
390
  softDistance,
769
- curveTextureVelocityEnabled,
770
- curveTextureRotationSpeedEnabled,
771
- curveTextureSizeEnabled,
772
- curveTextureOpacityEnabled,
773
391
  orientAxis,
774
- stretchBySpeed
775
- ]);
776
- const storage = useMemo2(() => {
777
- const arrays = {
778
- positions: instancedArray(activeMaxParticles, "vec3"),
779
- velocities: instancedArray(activeMaxParticles, "vec3"),
780
- lifetimes: instancedArray(activeMaxParticles, "float"),
781
- fadeRates: instancedArray(activeMaxParticles, "float"),
782
- particleSizes: instancedArray(activeMaxParticles, "float"),
783
- // Optional arrays - null when feature unused (saves GPU memory)
784
- particleRotations: null,
785
- particleColorStarts: null,
786
- particleColorEnds: null
787
- };
788
- if (activeFeatures.needsRotation) {
789
- arrays.particleRotations = instancedArray(activeMaxParticles, "vec3");
790
- }
791
- if (activeFeatures.needsPerParticleColor) {
792
- arrays.particleColorStarts = instancedArray(activeMaxParticles, "vec3");
793
- arrays.particleColorEnds = instancedArray(activeMaxParticles, "vec3");
794
- }
795
- return arrays;
796
- }, [
797
- activeMaxParticles,
798
- activeFeatures.needsRotation,
799
- activeFeatures.needsPerParticleColor
392
+ stretchBySpeed,
393
+ delay,
394
+ emitCount
800
395
  ]);
801
- const computeInit = useMemo2(
802
- () => createInitCompute(storage, activeMaxParticles),
803
- [storage, activeMaxParticles]
804
- );
805
- const computeSpawn = useMemo2(
806
- () => createSpawnCompute(storage, uniforms, activeMaxParticles),
807
- [storage, uniforms, activeMaxParticles]
808
- );
809
- const computeUpdate = useMemo2(
810
- () => createUpdateCompute(
811
- storage,
812
- uniforms,
813
- curveTexture,
814
- activeMaxParticles,
815
- {
816
- turbulence: activeFeatures.turbulence,
817
- attractors: activeFeatures.attractors,
818
- collision: activeFeatures.collision,
819
- rotation: activeFeatures.rotation,
820
- perParticleColor: activeFeatures.perParticleColor
821
- }
822
- ),
823
- [storage, uniforms, curveTexture, activeMaxParticles, activeFeatures]
824
- );
825
- const material = useMemo2(
826
- () => createParticleMaterial(storage, uniforms, curveTexture, {
827
- alphaMap,
828
- flipbook,
829
- appearance: activeAppearance,
830
- lighting: activeLighting,
831
- softParticles,
832
- geometry: activeGeometry,
833
- orientToDirection: activeOrientToDirection,
834
- shadow: activeShadow,
835
- blending,
836
- opacityNode,
837
- colorNode,
838
- backdropNode,
839
- alphaTestNode,
840
- castShadowNode
841
- }),
842
- [
843
- storage,
844
- uniforms,
845
- curveTexture,
846
- activeAppearance,
847
- alphaMap,
848
- flipbook,
849
- blending,
850
- activeGeometry,
851
- activeOrientToDirection,
852
- activeLighting,
853
- backdropNode,
854
- opacityNode,
855
- colorNode,
856
- alphaTestNode,
857
- castShadowNode,
858
- softParticles,
859
- activeShadow
860
- ]
861
- );
862
- const renderObject = useMemo2(() => {
863
- if (activeGeometry) {
864
- const mesh = new THREE.InstancedMesh(
865
- activeGeometry,
866
- material,
867
- activeMaxParticles
868
- );
869
- mesh.frustumCulled = false;
870
- mesh.castShadow = activeShadow;
871
- mesh.receiveShadow = activeShadow;
872
- return mesh;
873
- } else {
874
- const s = new THREE.Sprite(material);
875
- s.count = activeMaxParticles;
876
- s.frustumCulled = false;
877
- return s;
878
- }
879
- }, [material, activeMaxParticles, activeGeometry, activeShadow]);
880
- useEffect2(() => {
881
- if (!renderer || initialized.current) return;
882
- renderer.computeAsync(computeInit).then(() => {
883
- initialized.current = true;
884
- });
885
- }, [renderer, computeInit]);
886
- const applySpawnOverrides = useCallback(
887
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
888
- (overrides) => {
889
- if (!overrides) return null;
890
- const saved = {};
891
- const setUniform = (key, value) => {
892
- if (uniforms[key]) {
893
- saved[key] = uniforms[key].value;
894
- uniforms[key].value = value;
895
- }
896
- };
897
- if (overrides.size !== void 0) {
898
- const range = toRange(overrides.size, [0.1, 0.3]);
899
- setUniform("sizeMin", range[0]);
900
- setUniform("sizeMax", range[1]);
901
- }
902
- if (overrides.speed !== void 0) {
903
- const range = toRange(overrides.speed, [0.1, 0.1]);
904
- setUniform("speedMin", range[0]);
905
- setUniform("speedMax", range[1]);
906
- }
907
- if (overrides.lifetime !== void 0) {
908
- const range = toRange(overrides.lifetime, [1, 2]);
909
- setUniform("lifetimeMin", 1 / range[1]);
910
- setUniform("lifetimeMax", 1 / range[0]);
911
- }
912
- if (overrides.direction !== void 0) {
913
- const dir3D = toRotation3D(overrides.direction);
914
- setUniform("dirMinX", dir3D[0][0]);
915
- setUniform("dirMaxX", dir3D[0][1]);
916
- setUniform("dirMinY", dir3D[1][0]);
917
- setUniform("dirMaxY", dir3D[1][1]);
918
- setUniform("dirMinZ", dir3D[2][0]);
919
- setUniform("dirMaxZ", dir3D[2][1]);
920
- }
921
- if (overrides.startPosition !== void 0) {
922
- const pos3D = toRotation3D(overrides.startPosition);
923
- setUniform("startPosMinX", pos3D[0][0]);
924
- setUniform("startPosMaxX", pos3D[0][1]);
925
- setUniform("startPosMinY", pos3D[1][0]);
926
- setUniform("startPosMaxY", pos3D[1][1]);
927
- setUniform("startPosMinZ", pos3D[2][0]);
928
- setUniform("startPosMaxZ", pos3D[2][1]);
929
- }
930
- if (overrides.gravity !== void 0) {
931
- saved.gravity = uniforms.gravity.value.clone();
932
- uniforms.gravity.value.set(
933
- ...overrides.gravity
934
- );
935
- }
936
- const u = uniforms;
937
- if (overrides.colorStart !== void 0) {
938
- const colors = overrides.colorStart.slice(0, 8).map(hexToRgb);
939
- while (colors.length < 8)
940
- colors.push(colors[colors.length - 1] || [1, 1, 1]);
941
- setUniform("colorStartCount", overrides.colorStart.length);
942
- colors.forEach((c, i) => {
943
- if (u[`colorStart${i}`]) {
944
- saved[`colorStart${i}`] = u[`colorStart${i}`].value.clone();
945
- u[`colorStart${i}`].value.setRGB(...c);
946
- }
947
- });
948
- }
949
- if (overrides.colorEnd !== void 0) {
950
- const colors = overrides.colorEnd.slice(0, 8).map(hexToRgb);
951
- while (colors.length < 8)
952
- colors.push(colors[colors.length - 1] || [1, 1, 1]);
953
- setUniform("colorEndCount", overrides.colorEnd.length);
954
- colors.forEach((c, i) => {
955
- if (u[`colorEnd${i}`]) {
956
- saved[`colorEnd${i}`] = u[`colorEnd${i}`].value.clone();
957
- u[`colorEnd${i}`].value.setRGB(...c);
958
- }
959
- });
960
- }
961
- if (overrides.rotation !== void 0) {
962
- const rot3D = toRotation3D(overrides.rotation);
963
- setUniform("rotationMinX", rot3D[0][0]);
964
- setUniform("rotationMaxX", rot3D[0][1]);
965
- setUniform("rotationMinY", rot3D[1][0]);
966
- setUniform("rotationMaxY", rot3D[1][1]);
967
- setUniform("rotationMinZ", rot3D[2][0]);
968
- setUniform("rotationMaxZ", rot3D[2][1]);
969
- }
970
- if (overrides.emitterShape !== void 0) {
971
- setUniform("emitterShapeType", overrides.emitterShape);
972
- }
973
- if (overrides.emitterRadius !== void 0) {
974
- const range = toRange(overrides.emitterRadius, [0, 1]);
975
- setUniform("emitterRadiusInner", range[0]);
976
- setUniform("emitterRadiusOuter", range[1]);
977
- }
978
- if (overrides.emitterAngle !== void 0) {
979
- setUniform("emitterAngle", overrides.emitterAngle);
980
- }
981
- if (overrides.emitterHeight !== void 0) {
982
- const range = toRange(overrides.emitterHeight, [0, 1]);
983
- setUniform("emitterHeightMin", range[0]);
984
- setUniform("emitterHeightMax", range[1]);
985
- }
986
- if (overrides.emitterSurfaceOnly !== void 0) {
987
- setUniform("emitterSurfaceOnly", overrides.emitterSurfaceOnly ? 1 : 0);
988
- }
989
- if (overrides.emitterDirection !== void 0) {
990
- saved.emitterDir = uniforms.emitterDir.value.clone();
991
- uniforms.emitterDir.value.set(...overrides.emitterDirection).normalize();
992
- }
993
- return () => {
994
- Object.entries(saved).forEach(([key, value]) => {
995
- if (uniforms[key]) {
996
- uniforms[key].value = value;
997
- }
998
- });
999
- };
1000
- },
1001
- [uniforms]
1002
- );
1003
- const spawnInternal = useCallback(
1004
- (x, y, z, count = 20, overrides = null) => {
1005
- if (!initialized.current || !renderer) return;
1006
- const restore = applySpawnOverrides(overrides);
1007
- const startIdx = nextIndex.current;
1008
- const endIdx = (startIdx + count) % activeMaxParticles;
1009
- uniforms.spawnPosition.value.set(x, y, z);
1010
- uniforms.spawnIndexStart.value = startIdx;
1011
- uniforms.spawnIndexEnd.value = endIdx;
1012
- uniforms.spawnSeed.value = Math.random() * 1e4;
1013
- nextIndex.current = endIdx;
1014
- renderer.computeAsync(computeSpawn);
1015
- if (restore) restore();
1016
- },
1017
- [
1018
- renderer,
1019
- computeSpawn,
1020
- uniforms,
1021
- activeMaxParticles,
1022
- applySpawnOverrides
1023
- ]
1024
- );
1025
396
  const spawn = useCallback(
1026
397
  (x = 0, y = 0, z = 0, count = 20, overrides = null) => {
1027
- var _a2;
1028
- const [px, py, pz] = (_a2 = positionRef.current) != null ? _a2 : [0, 0, 0];
1029
- spawnInternal(px + x, py + y, pz + z, count, overrides);
398
+ const [px, py, pz] = system.position;
399
+ system.spawn(px + x, py + y, pz + z, count, overrides);
1030
400
  },
1031
- [spawnInternal]
401
+ [system]
1032
402
  );
1033
- const computeUpdateRef = useRef2(computeUpdate);
1034
- useEffect2(() => {
1035
- computeUpdateRef.current = computeUpdate;
1036
- }, [computeUpdate]);
1037
- useFrame(async (state, delta) => {
1038
- var _a2, _b;
1039
- if (!initialized.current || !renderer) return;
1040
- uniforms.deltaTime.value = delta;
1041
- const turbSpeed = (_b = (_a2 = turbulenceRef.current) == null ? void 0 : _a2.speed) != null ? _b : 1;
1042
- uniforms.turbulenceTime.value += delta * turbSpeed;
1043
- await renderer.computeAsync(computeUpdateRef.current);
403
+ useFrame(async (_state, delta) => {
404
+ if (!system.initialized) return;
405
+ await system.update(delta);
1044
406
  if (emitting) {
1045
- const [px, py, pz] = positionRef.current;
1046
- const currentDelay = delayRef.current;
1047
- const currentEmitCount = emitCountRef.current;
1048
- if (!currentDelay) {
1049
- spawnInternal(px, py, pz, currentEmitCount);
1050
- } else {
1051
- emitAccumulator.current += delta;
1052
- if (emitAccumulator.current >= currentDelay) {
1053
- emitAccumulator.current -= currentDelay;
1054
- spawnInternal(px, py, pz, currentEmitCount);
1055
- }
1056
- }
407
+ system.autoEmit(delta);
1057
408
  }
1058
409
  });
1059
410
  const start = useCallback(() => {
411
+ system.start();
1060
412
  setEmitting(true);
1061
- emitAccumulator.current = 0;
1062
- }, []);
413
+ }, [system]);
1063
414
  const stop = useCallback(() => {
415
+ system.stop();
1064
416
  setEmitting(false);
1065
- }, []);
1066
- const prevMaterialRef = useRef2(null);
1067
- const prevRenderObjectRef = useRef2(null);
1068
- useEffect2(() => {
1069
- if (prevMaterialRef.current && prevMaterialRef.current !== material) {
417
+ }, [system]);
418
+ const prevMaterialRef = useRef(null);
419
+ const prevRenderObjectRef = useRef(null);
420
+ useEffect(() => {
421
+ if (prevMaterialRef.current && prevMaterialRef.current !== system.material) {
1070
422
  prevMaterialRef.current.dispose();
1071
423
  }
1072
- prevMaterialRef.current = material;
1073
- if (prevRenderObjectRef.current && prevRenderObjectRef.current !== renderObject) {
424
+ prevMaterialRef.current = system.material;
425
+ if (prevRenderObjectRef.current && prevRenderObjectRef.current !== system.renderObject) {
1074
426
  if (prevRenderObjectRef.current.material) {
1075
427
  prevRenderObjectRef.current.material.dispose();
1076
428
  }
1077
429
  }
1078
- prevRenderObjectRef.current = renderObject;
1079
- }, [material, renderObject]);
1080
- useEffect2(() => {
430
+ prevRenderObjectRef.current = system.renderObject;
431
+ }, [system.material, system.renderObject]);
432
+ useEffect(() => {
1081
433
  return () => {
1082
- if (material) {
1083
- material.dispose();
1084
- }
1085
- if (renderObject) {
1086
- if (renderObject.geometry && !geometry) {
1087
- renderObject.geometry.dispose();
1088
- }
1089
- if (renderObject.material) {
1090
- renderObject.material.dispose();
1091
- }
1092
- }
1093
- initialized.current = false;
1094
- nextIndex.current = 0;
434
+ system.dispose();
1095
435
  };
1096
436
  }, []);
1097
- const particleAPI = useMemo2(
437
+ const particleAPI = useMemo(
1098
438
  () => ({
1099
439
  spawn,
1100
440
  start,
@@ -1103,266 +443,93 @@ var VFXParticles = forwardRef(
1103
443
  return emitting;
1104
444
  },
1105
445
  clear() {
1106
- renderer.computeAsync(computeInit);
1107
- nextIndex.current = 0;
446
+ system.clear();
1108
447
  },
1109
- uniforms
448
+ uniforms: system.uniforms
1110
449
  }),
1111
- [spawn, start, stop, emitting, renderer, computeInit, uniforms]
450
+ [spawn, start, stop, emitting, system]
1112
451
  );
1113
452
  useImperativeHandle(ref, () => particleAPI, [particleAPI]);
1114
453
  const registerParticles = useVFXStore((s) => s.registerParticles);
1115
454
  const unregisterParticles = useVFXStore((s) => s.unregisterParticles);
1116
- useEffect2(() => {
455
+ useEffect(() => {
1117
456
  if (!name) return;
1118
457
  registerParticles(name, particleAPI);
1119
458
  return () => {
1120
459
  unregisterParticles(name);
1121
460
  };
1122
461
  }, [name, particleAPI, registerParticles, unregisterParticles]);
1123
- const debugValuesRef = useRef2(null);
1124
- const prevGeometryTypeRef = useRef2(null);
1125
- const prevGeometryArgsRef = useRef2(null);
462
+ const prevGeometryTypeRef = useRef(null);
463
+ const prevGeometryArgsRef = useRef(null);
1126
464
  const handleDebugUpdate = useCallback(
1127
465
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
1128
466
  (newValues) => {
1129
- var _a2, _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;
467
+ var _a2, _b, _c, _d, _e, _f, _g, _h, _i, _j;
1130
468
  debugValuesRef.current = __spreadValues(__spreadValues({}, debugValuesRef.current), newValues);
1131
- if ("size" in newValues) {
1132
- const sizeR = toRange(newValues.size, [0.1, 0.3]);
1133
- uniforms.sizeMin.value = sizeR[0];
1134
- uniforms.sizeMax.value = sizeR[1];
1135
- }
1136
- if ("fadeSize" in newValues) {
1137
- const fadeSizeR = toRange(newValues.fadeSize, [1, 0]);
1138
- uniforms.fadeSizeStart.value = fadeSizeR[0];
1139
- uniforms.fadeSizeEnd.value = fadeSizeR[1];
469
+ if ("colorStart" in newValues && newValues.colorStart) {
470
+ const currentColorEnd = (_a2 = debugValuesRef.current) == null ? void 0 : _a2.colorEnd;
471
+ if (!currentColorEnd) {
472
+ newValues = __spreadProps(__spreadValues({}, newValues), {
473
+ colorEnd: null
474
+ });
475
+ }
1140
476
  }
1141
- if ("fadeOpacity" in newValues) {
1142
- const fadeOpacityR = toRange(newValues.fadeOpacity, [1, 0]);
1143
- uniforms.fadeOpacityStart.value = fadeOpacityR[0];
1144
- uniforms.fadeOpacityEnd.value = fadeOpacityR[1];
477
+ if ("colorEnd" in newValues && !newValues.colorEnd) {
478
+ newValues = __spreadProps(__spreadValues({}, newValues), {
479
+ colorEnd: null,
480
+ // Ensure updateUniformsPartial gets the right fallback
481
+ colorStart: (_d = (_c = newValues.colorStart) != null ? _c : (_b = debugValuesRef.current) == null ? void 0 : _b.colorStart) != null ? _d : ["#ffffff"]
482
+ });
1145
483
  }
484
+ updateUniformsPartial(system.uniforms, newValues);
1146
485
  if ("fadeSizeCurve" in newValues) {
1147
486
  setActiveFadeSizeCurve(newValues.fadeSizeCurve);
1148
- uniforms.fadeSizeCurveEnabled.value = newValues.fadeSizeCurve ? 1 : 0;
1149
487
  }
1150
488
  if ("fadeOpacityCurve" in newValues) {
1151
489
  setActiveFadeOpacityCurve(newValues.fadeOpacityCurve);
1152
- uniforms.fadeOpacityCurveEnabled.value = newValues.fadeOpacityCurve ? 1 : 0;
1153
490
  }
1154
491
  if ("velocityCurve" in newValues) {
1155
492
  setActiveVelocityCurve(newValues.velocityCurve);
1156
- uniforms.velocityCurveEnabled.value = newValues.velocityCurve ? 1 : 0;
1157
493
  }
1158
494
  if ("rotationSpeedCurve" in newValues) {
1159
495
  setActiveRotationSpeedCurve(newValues.rotationSpeedCurve);
1160
- uniforms.rotationSpeedCurveEnabled.value = newValues.rotationSpeedCurve ? 1 : 0;
1161
- }
1162
- if ("orientAxis" in newValues) {
1163
- uniforms.orientAxisType.value = axisToNumber(newValues.orientAxis);
1164
- }
1165
- if ("stretchBySpeed" in newValues) {
1166
- uniforms.stretchEnabled.value = newValues.stretchBySpeed ? 1 : 0;
1167
- uniforms.stretchFactor.value = (_b = (_a2 = newValues.stretchBySpeed) == null ? void 0 : _a2.factor) != null ? _b : 1;
1168
- uniforms.stretchMax.value = (_d = (_c = newValues.stretchBySpeed) == null ? void 0 : _c.maxStretch) != null ? _d : 5;
1169
- }
1170
- if (newValues.gravity && Array.isArray(newValues.gravity)) {
1171
- uniforms.gravity.value.x = newValues.gravity[0];
1172
- uniforms.gravity.value.y = newValues.gravity[1];
1173
- uniforms.gravity.value.z = newValues.gravity[2];
1174
- }
1175
- if ("speed" in newValues) {
1176
- const speedR = toRange(newValues.speed, [0.1, 0.1]);
1177
- uniforms.speedMin.value = speedR[0];
1178
- uniforms.speedMax.value = speedR[1];
1179
- }
1180
- if ("lifetime" in newValues) {
1181
- const lifetimeR = toRange(newValues.lifetime, [1, 2]);
1182
- uniforms.lifetimeMin.value = 1 / lifetimeR[1];
1183
- uniforms.lifetimeMax.value = 1 / lifetimeR[0];
1184
- }
1185
- if ("friction" in newValues && newValues.friction) {
1186
- const frictionR = toRange(newValues.friction.intensity, [0, 0]);
1187
- uniforms.frictionIntensityStart.value = frictionR[0];
1188
- uniforms.frictionIntensityEnd.value = frictionR[1];
1189
- uniforms.frictionEasingType.value = easingToType(
1190
- newValues.friction.easing
1191
- );
1192
- }
1193
- if ("direction" in newValues) {
1194
- const dir3D = toRotation3D(newValues.direction);
1195
- uniforms.dirMinX.value = dir3D[0][0];
1196
- uniforms.dirMaxX.value = dir3D[0][1];
1197
- uniforms.dirMinY.value = dir3D[1][0];
1198
- uniforms.dirMaxY.value = dir3D[1][1];
1199
- uniforms.dirMinZ.value = dir3D[2][0];
1200
- uniforms.dirMaxZ.value = dir3D[2][1];
1201
- }
1202
- if ("startPosition" in newValues) {
1203
- const startPos3D = toRotation3D(newValues.startPosition);
1204
- uniforms.startPosMinX.value = startPos3D[0][0];
1205
- uniforms.startPosMaxX.value = startPos3D[0][1];
1206
- uniforms.startPosMinY.value = startPos3D[1][0];
1207
- uniforms.startPosMaxY.value = startPos3D[1][1];
1208
- uniforms.startPosMinZ.value = startPos3D[2][0];
1209
- uniforms.startPosMaxZ.value = startPos3D[2][1];
1210
- }
1211
- if ("rotation" in newValues) {
1212
- const rot3D = toRotation3D(newValues.rotation);
1213
- uniforms.rotationMinX.value = rot3D[0][0];
1214
- uniforms.rotationMaxX.value = rot3D[0][1];
1215
- uniforms.rotationMinY.value = rot3D[1][0];
1216
- uniforms.rotationMaxY.value = rot3D[1][1];
1217
- uniforms.rotationMinZ.value = rot3D[2][0];
1218
- uniforms.rotationMaxZ.value = rot3D[2][1];
1219
- }
1220
- if ("rotationSpeed" in newValues) {
1221
- const rotSpeed3D = toRotation3D(newValues.rotationSpeed);
1222
- uniforms.rotationSpeedMinX.value = rotSpeed3D[0][0];
1223
- uniforms.rotationSpeedMaxX.value = rotSpeed3D[0][1];
1224
- uniforms.rotationSpeedMinY.value = rotSpeed3D[1][0];
1225
- uniforms.rotationSpeedMaxY.value = rotSpeed3D[1][1];
1226
- uniforms.rotationSpeedMinZ.value = rotSpeed3D[2][0];
1227
- uniforms.rotationSpeedMaxZ.value = rotSpeed3D[2][1];
1228
- }
1229
- if ("rotation" in newValues || "rotationSpeed" in newValues) {
1230
- const rot = (_g = (_f = newValues.rotation) != null ? _f : (_e = debugValuesRef.current) == null ? void 0 : _e.rotation) != null ? _g : [0, 0];
1231
- const rotSpeed = (_j = (_i = newValues.rotationSpeed) != null ? _i : (_h = debugValuesRef.current) == null ? void 0 : _h.rotationSpeed) != null ? _j : [0, 0];
1232
- const needsRotation = isNonDefaultRotation(rot) || isNonDefaultRotation(rotSpeed);
1233
- if (needsRotation !== activeNeedsRotation) {
1234
- setActiveNeedsRotation(needsRotation);
1235
- }
1236
- }
1237
- if ("intensity" in newValues) {
1238
- uniforms.intensity.value = newValues.intensity || 1;
1239
- }
1240
- if ("colorStart" in newValues && newValues.colorStart) {
1241
- const startColors2 = newValues.colorStart.slice(0, 8).map(hexToRgb);
1242
- while (startColors2.length < 8)
1243
- startColors2.push(startColors2[startColors2.length - 1] || [1, 1, 1]);
1244
- uniforms.colorStartCount.value = newValues.colorStart.length;
1245
- startColors2.forEach((c, i) => {
1246
- if (uniforms[`colorStart${i}`]) {
1247
- uniforms[`colorStart${i}`].value.setRGB(...c);
1248
- }
1249
- });
1250
- const currentColorEnd = (_k = debugValuesRef.current) == null ? void 0 : _k.colorEnd;
1251
- if (!currentColorEnd) {
1252
- uniforms.colorEndCount.value = newValues.colorStart.length;
1253
- startColors2.forEach((c, i) => {
1254
- if (uniforms[`colorEnd${i}`]) {
1255
- uniforms[`colorEnd${i}`].value.setRGB(...c);
1256
- }
1257
- });
1258
- }
1259
- }
1260
- if ("colorEnd" in newValues) {
1261
- const effectiveEndColors = newValues.colorEnd || newValues.colorStart || ((_l = debugValuesRef.current) == null ? void 0 : _l.colorStart) || ["#ffffff"];
1262
- if (effectiveEndColors) {
1263
- const endColors2 = effectiveEndColors.slice(0, 8).map(hexToRgb);
1264
- while (endColors2.length < 8)
1265
- endColors2.push(endColors2[endColors2.length - 1] || [1, 1, 1]);
1266
- uniforms.colorEndCount.value = effectiveEndColors.length;
1267
- endColors2.forEach((c, i) => {
1268
- if (uniforms[`colorEnd${i}`]) {
1269
- uniforms[`colorEnd${i}`].value.setRGB(...c);
1270
- }
1271
- });
1272
- }
1273
- }
1274
- if ("colorStart" in newValues || "colorEnd" in newValues) {
1275
- const startLen = (_q = (_p = (_m = newValues.colorStart) == null ? void 0 : _m.length) != null ? _p : (_o = (_n = debugValuesRef.current) == null ? void 0 : _n.colorStart) == null ? void 0 : _o.length) != null ? _q : 1;
1276
- const hasColorEnd = "colorEnd" in newValues ? newValues.colorEnd !== null : ((_r = debugValuesRef.current) == null ? void 0 : _r.colorEnd) !== null;
1277
- const needsPerParticle = startLen > 1 || hasColorEnd;
1278
- if (needsPerParticle !== activeNeedsPerParticleColor) {
1279
- setActiveNeedsPerParticleColor(needsPerParticle);
1280
- }
1281
- }
1282
- if ("emitterShape" in newValues) {
1283
- uniforms.emitterShapeType.value = (_s = newValues.emitterShape) != null ? _s : EmitterShape.BOX;
1284
- }
1285
- if ("emitterRadius" in newValues) {
1286
- const emitterRadiusR = toRange(newValues.emitterRadius, [0, 1]);
1287
- uniforms.emitterRadiusInner.value = emitterRadiusR[0];
1288
- uniforms.emitterRadiusOuter.value = emitterRadiusR[1];
1289
- }
1290
- if ("emitterAngle" in newValues) {
1291
- uniforms.emitterAngle.value = (_t = newValues.emitterAngle) != null ? _t : Math.PI / 4;
1292
- }
1293
- if ("emitterHeight" in newValues) {
1294
- const emitterHeightR = toRange(newValues.emitterHeight, [0, 1]);
1295
- uniforms.emitterHeightMin.value = emitterHeightR[0];
1296
- uniforms.emitterHeightMax.value = emitterHeightR[1];
1297
- }
1298
- if ("emitterSurfaceOnly" in newValues) {
1299
- uniforms.emitterSurfaceOnly.value = newValues.emitterSurfaceOnly ? 1 : 0;
1300
- }
1301
- if ("emitterDirection" in newValues && newValues.emitterDirection && Array.isArray(newValues.emitterDirection)) {
1302
- const dir = new THREE.Vector3(
1303
- ...newValues.emitterDirection
1304
- ).normalize();
1305
- uniforms.emitterDir.value.x = dir.x;
1306
- uniforms.emitterDir.value.y = dir.y;
1307
- uniforms.emitterDir.value.z = dir.z;
1308
496
  }
1309
497
  if ("turbulence" in newValues) {
1310
- uniforms.turbulenceIntensity.value = (_v = (_u = newValues.turbulence) == null ? void 0 : _u.intensity) != null ? _v : 0;
1311
- uniforms.turbulenceFrequency.value = (_x = (_w = newValues.turbulence) == null ? void 0 : _w.frequency) != null ? _x : 1;
1312
- uniforms.turbulenceSpeed.value = (_z = (_y = newValues.turbulence) == null ? void 0 : _y.speed) != null ? _z : 1;
1313
- turbulenceRef.current = newValues.turbulence;
1314
- const needsTurbulence = newValues.turbulence !== null && ((_B = (_A = newValues.turbulence) == null ? void 0 : _A.intensity) != null ? _B : 0) > 0;
1315
- if (needsTurbulence !== activeTurbulence) {
1316
- setActiveTurbulence(needsTurbulence);
1317
- }
498
+ system.setTurbulenceSpeed((_f = (_e = newValues.turbulence) == null ? void 0 : _e.speed) != null ? _f : 1);
1318
499
  }
1319
- if ("attractToCenter" in newValues) {
1320
- uniforms.attractToCenter.value = newValues.attractToCenter ? 1 : 0;
500
+ const newFeatures = resolveFeatures(debugValuesRef.current);
501
+ if (newFeatures.needsRotation !== activeNeedsRotation) {
502
+ setActiveNeedsRotation(newFeatures.needsRotation);
1321
503
  }
1322
- if ("startPositionAsDirection" in newValues) {
1323
- uniforms.startPositionAsDirection.value = newValues.startPositionAsDirection ? 1 : 0;
504
+ if (newFeatures.needsPerParticleColor !== activeNeedsPerParticleColor) {
505
+ setActiveNeedsPerParticleColor(newFeatures.needsPerParticleColor);
1324
506
  }
1325
- if ("softParticles" in newValues) {
1326
- uniforms.softParticlesEnabled.value = newValues.softParticles ? 1 : 0;
507
+ if (newFeatures.turbulence !== activeTurbulence) {
508
+ setActiveTurbulence(newFeatures.turbulence);
1327
509
  }
1328
- if ("softDistance" in newValues) {
1329
- uniforms.softDistance.value = (_C = newValues.softDistance) != null ? _C : 0.5;
510
+ if (newFeatures.attractors !== activeAttractors) {
511
+ setActiveAttractors(newFeatures.attractors);
1330
512
  }
1331
- if ("collision" in newValues) {
1332
- uniforms.collisionEnabled.value = newValues.collision ? 1 : 0;
1333
- uniforms.collisionPlaneY.value = (_F = (_E = (_D = newValues.collision) == null ? void 0 : _D.plane) == null ? void 0 : _E.y) != null ? _F : 0;
1334
- uniforms.collisionBounce.value = (_H = (_G = newValues.collision) == null ? void 0 : _G.bounce) != null ? _H : 0.3;
1335
- uniforms.collisionFriction.value = (_J = (_I = newValues.collision) == null ? void 0 : _I.friction) != null ? _J : 0.8;
1336
- uniforms.collisionDie.value = ((_K = newValues.collision) == null ? void 0 : _K.die) ? 1 : 0;
1337
- uniforms.sizeBasedGravity.value = (_M = (_L = newValues.collision) == null ? void 0 : _L.sizeBasedGravity) != null ? _M : 0;
1338
- const needsCollision = newValues.collision !== null && newValues.collision !== void 0;
1339
- if (needsCollision !== activeCollision) {
1340
- setActiveCollision(needsCollision);
1341
- }
1342
- }
1343
- if ("attractors" in newValues) {
1344
- const needsAttractors = newValues.attractors !== null && ((_N = newValues.attractors) == null ? void 0 : _N.length) > 0;
1345
- if (needsAttractors !== activeAttractors) {
1346
- setActiveAttractors(needsAttractors);
1347
- }
513
+ if (newFeatures.collision !== activeCollision) {
514
+ setActiveCollision(newFeatures.collision);
1348
515
  }
1349
516
  if (newValues.position) {
1350
- positionRef.current = newValues.position;
517
+ system.setPosition(newValues.position);
1351
518
  }
1352
- if ("delay" in newValues) delayRef.current = (_O = newValues.delay) != null ? _O : 0;
519
+ if ("delay" in newValues) system.setDelay((_g = newValues.delay) != null ? _g : 0);
1353
520
  if ("emitCount" in newValues)
1354
- emitCountRef.current = (_P = newValues.emitCount) != null ? _P : 1;
521
+ system.setEmitCount((_h = newValues.emitCount) != null ? _h : 1);
1355
522
  if (newValues.autoStart !== void 0) {
1356
523
  setEmitting(newValues.autoStart);
1357
524
  }
1358
- if (material && newValues.blending !== void 0) {
1359
- material.blending = newValues.blending;
1360
- material.needsUpdate = true;
525
+ if (system.material && newValues.blending !== void 0) {
526
+ system.material.blending = newValues.blending;
527
+ system.material.needsUpdate = true;
1361
528
  }
1362
529
  if (newValues.maxParticles !== void 0 && newValues.maxParticles !== activeMaxParticles) {
1363
530
  setActiveMaxParticles(newValues.maxParticles);
1364
- initialized.current = false;
1365
- nextIndex.current = 0;
531
+ system.initialized = false;
532
+ system.nextIndex = 0;
1366
533
  }
1367
534
  if (newValues.lighting !== void 0 && newValues.lighting !== activeLighting) {
1368
535
  setActiveLighting(newValues.lighting);
@@ -1377,8 +544,8 @@ var VFXParticles = forwardRef(
1377
544
  setActiveShadow(newValues.shadow);
1378
545
  }
1379
546
  if ("geometryType" in newValues || "geometryArgs" in newValues) {
1380
- const geoType = (_Q = newValues.geometryType) != null ? _Q : prevGeometryTypeRef.current;
1381
- const geoArgs = (_R = newValues.geometryArgs) != null ? _R : prevGeometryArgsRef.current;
547
+ const geoType = (_i = newValues.geometryType) != null ? _i : prevGeometryTypeRef.current;
548
+ const geoArgs = (_j = newValues.geometryArgs) != null ? _j : prevGeometryArgsRef.current;
1382
549
  const geoTypeChanged = "geometryType" in newValues && geoType !== prevGeometryTypeRef.current;
1383
550
  const geoArgsChanged = "geometryArgs" in newValues && JSON.stringify(geoArgs) !== JSON.stringify(prevGeometryArgsRef.current);
1384
551
  if (geoTypeChanged || geoArgsChanged) {
@@ -1404,9 +571,7 @@ var VFXParticles = forwardRef(
1404
571
  }
1405
572
  },
1406
573
  [
1407
- uniforms,
1408
- material,
1409
- renderObject,
574
+ system,
1410
575
  activeMaxParticles,
1411
576
  activeLighting,
1412
577
  activeAppearance,
@@ -1421,202 +586,68 @@ var VFXParticles = forwardRef(
1421
586
  geometry
1422
587
  ]
1423
588
  );
1424
- useEffect2(() => {
589
+ useEffect(() => {
1425
590
  if (!debug) return;
1426
- const initialValues = __spreadValues({
1427
- name,
1428
- // Include name for curve baking filename
1429
- maxParticles,
1430
- size,
1431
- colorStart,
1432
- colorEnd,
1433
- fadeSize,
1434
- fadeSizeCurve: fadeSizeCurve || null,
1435
- // null = linear (no curve)
1436
- fadeOpacity,
1437
- fadeOpacityCurve: fadeOpacityCurve || null,
1438
- // null = linear (no curve)
1439
- velocityCurve: velocityCurve || null,
1440
- // null = use friction (no curve)
1441
- gravity,
1442
- lifetime,
1443
- direction,
1444
- startPosition,
1445
- startPositionAsDirection,
1446
- speed,
1447
- friction,
1448
- appearance,
1449
- rotation,
1450
- rotationSpeed,
1451
- rotationSpeedCurve: rotationSpeedCurve || null,
1452
- // null = constant speed (no curve)
1453
- orientToDirection,
1454
- orientAxis,
1455
- stretchBySpeed: stretchBySpeed || null,
1456
- lighting,
1457
- shadow,
1458
- blending,
1459
- intensity,
1460
- position,
1461
- autoStart,
1462
- delay,
1463
- emitCount,
1464
- emitterShape,
1465
- emitterRadius,
1466
- emitterAngle,
1467
- emitterHeight,
1468
- emitterSurfaceOnly,
1469
- emitterDirection,
1470
- turbulence,
1471
- attractToCenter,
1472
- softParticles,
1473
- softDistance,
1474
- collision
1475
- }, detectGeometryTypeAndArgs(geometry));
1476
- function detectGeometryTypeAndArgs(geo) {
1477
- var _a2, _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;
1478
- if (!geo) return { geometryType: "none", geometryArgs: null };
1479
- const name2 = geo.constructor.name;
1480
- const params = geo.parameters || {};
1481
- switch (name2) {
1482
- case "BoxGeometry":
1483
- return {
1484
- geometryType: "box",
1485
- geometryArgs: {
1486
- width: (_a2 = params.width) != null ? _a2 : 1,
1487
- height: (_b = params.height) != null ? _b : 1,
1488
- depth: (_c = params.depth) != null ? _c : 1,
1489
- widthSegments: (_d = params.widthSegments) != null ? _d : 1,
1490
- heightSegments: (_e = params.heightSegments) != null ? _e : 1,
1491
- depthSegments: (_f = params.depthSegments) != null ? _f : 1
1492
- }
1493
- };
1494
- case "SphereGeometry":
1495
- return {
1496
- geometryType: "sphere",
1497
- geometryArgs: {
1498
- radius: (_g = params.radius) != null ? _g : 0.5,
1499
- widthSegments: (_h = params.widthSegments) != null ? _h : 16,
1500
- heightSegments: (_i = params.heightSegments) != null ? _i : 12
1501
- }
1502
- };
1503
- case "CylinderGeometry":
1504
- return {
1505
- geometryType: "cylinder",
1506
- geometryArgs: {
1507
- radiusTop: (_j = params.radiusTop) != null ? _j : 0.5,
1508
- radiusBottom: (_k = params.radiusBottom) != null ? _k : 0.5,
1509
- height: (_l = params.height) != null ? _l : 1,
1510
- radialSegments: (_m = params.radialSegments) != null ? _m : 16,
1511
- heightSegments: (_n = params.heightSegments) != null ? _n : 1
1512
- }
1513
- };
1514
- case "ConeGeometry":
1515
- return {
1516
- geometryType: "cone",
1517
- geometryArgs: {
1518
- radius: (_o = params.radius) != null ? _o : 0.5,
1519
- height: (_p = params.height) != null ? _p : 1,
1520
- radialSegments: (_q = params.radialSegments) != null ? _q : 16,
1521
- heightSegments: (_r = params.heightSegments) != null ? _r : 1
1522
- }
1523
- };
1524
- case "TorusGeometry":
1525
- return {
1526
- geometryType: "torus",
1527
- geometryArgs: {
1528
- radius: (_s = params.radius) != null ? _s : 0.5,
1529
- tube: (_t = params.tube) != null ? _t : 0.2,
1530
- radialSegments: (_u = params.radialSegments) != null ? _u : 12,
1531
- tubularSegments: (_v = params.tubularSegments) != null ? _v : 24
1532
- }
1533
- };
1534
- case "PlaneGeometry":
1535
- return {
1536
- geometryType: "plane",
1537
- geometryArgs: {
1538
- width: (_w = params.width) != null ? _w : 1,
1539
- height: (_x = params.height) != null ? _x : 1,
1540
- widthSegments: (_y = params.widthSegments) != null ? _y : 1,
1541
- heightSegments: (_z = params.heightSegments) != null ? _z : 1
1542
- }
1543
- };
1544
- case "CircleGeometry":
1545
- return {
1546
- geometryType: "circle",
1547
- geometryArgs: {
1548
- radius: (_A = params.radius) != null ? _A : 0.5,
1549
- segments: (_B = params.segments) != null ? _B : 16
1550
- }
1551
- };
1552
- case "RingGeometry":
1553
- return {
1554
- geometryType: "ring",
1555
- geometryArgs: {
1556
- innerRadius: (_C = params.innerRadius) != null ? _C : 0.25,
1557
- outerRadius: (_D = params.outerRadius) != null ? _D : 0.5,
1558
- thetaSegments: (_E = params.thetaSegments) != null ? _E : 16
1559
- }
1560
- };
1561
- case "DodecahedronGeometry":
1562
- return {
1563
- geometryType: "dodecahedron",
1564
- geometryArgs: {
1565
- radius: (_F = params.radius) != null ? _F : 0.5,
1566
- detail: (_G = params.detail) != null ? _G : 0
1567
- }
1568
- };
1569
- case "IcosahedronGeometry":
1570
- return {
1571
- geometryType: "icosahedron",
1572
- geometryArgs: {
1573
- radius: (_H = params.radius) != null ? _H : 0.5,
1574
- detail: (_I = params.detail) != null ? _I : 0
1575
- }
1576
- };
1577
- case "OctahedronGeometry":
1578
- return {
1579
- geometryType: "octahedron",
1580
- geometryArgs: {
1581
- radius: (_J = params.radius) != null ? _J : 0.5,
1582
- detail: (_K = params.detail) != null ? _K : 0
1583
- }
1584
- };
1585
- case "TetrahedronGeometry":
1586
- return {
1587
- geometryType: "tetrahedron",
1588
- geometryArgs: {
1589
- radius: (_L = params.radius) != null ? _L : 0.5,
1590
- detail: (_M = params.detail) != null ? _M : 0
1591
- }
1592
- };
1593
- case "CapsuleGeometry":
1594
- return {
1595
- geometryType: "capsule",
1596
- geometryArgs: {
1597
- radius: (_N = params.radius) != null ? _N : 0.25,
1598
- length: (_O = params.length) != null ? _O : 0.5,
1599
- capSegments: (_P = params.capSegments) != null ? _P : 4,
1600
- radialSegments: (_Q = params.radialSegments) != null ? _Q : 8
1601
- }
1602
- };
1603
- default:
1604
- return { geometryType: "none", geometryArgs: null };
591
+ import("debug-vfx").then(
592
+ ({ renderDebugPanel, detectGeometryTypeAndArgs }) => {
593
+ const initialValues = __spreadValues({
594
+ name,
595
+ maxParticles,
596
+ size,
597
+ colorStart,
598
+ colorEnd,
599
+ fadeSize,
600
+ fadeSizeCurve: fadeSizeCurve || null,
601
+ fadeOpacity,
602
+ fadeOpacityCurve: fadeOpacityCurve || null,
603
+ velocityCurve: velocityCurve || null,
604
+ gravity,
605
+ lifetime,
606
+ direction,
607
+ startPosition,
608
+ startPositionAsDirection,
609
+ speed,
610
+ friction,
611
+ appearance,
612
+ rotation,
613
+ rotationSpeed,
614
+ rotationSpeedCurve: rotationSpeedCurve || null,
615
+ orientToDirection,
616
+ orientAxis,
617
+ stretchBySpeed: stretchBySpeed || null,
618
+ lighting,
619
+ shadow,
620
+ blending,
621
+ intensity,
622
+ position,
623
+ autoStart,
624
+ delay,
625
+ emitCount,
626
+ emitterShape,
627
+ emitterRadius,
628
+ emitterAngle,
629
+ emitterHeight,
630
+ emitterSurfaceOnly,
631
+ emitterDirection,
632
+ turbulence,
633
+ attractToCenter,
634
+ softParticles,
635
+ softDistance,
636
+ collision
637
+ }, detectGeometryTypeAndArgs(geometry));
638
+ debugValuesRef.current = initialValues;
639
+ prevGeometryTypeRef.current = initialValues.geometryType;
640
+ prevGeometryArgsRef.current = initialValues.geometryArgs;
641
+ renderDebugPanel(initialValues, handleDebugUpdate);
1605
642
  }
1606
- }
1607
- debugValuesRef.current = initialValues;
1608
- prevGeometryTypeRef.current = initialValues.geometryType;
1609
- prevGeometryArgsRef.current = initialValues.geometryArgs;
1610
- import("debug-vfx").then(({ renderDebugPanel }) => {
1611
- renderDebugPanel(initialValues, handleDebugUpdate);
1612
- });
643
+ );
1613
644
  return () => {
1614
645
  import("debug-vfx").then(({ destroyDebugPanel }) => {
1615
646
  destroyDebugPanel();
1616
647
  });
1617
648
  };
1618
649
  }, [debug, geometry]);
1619
- useEffect2(() => {
650
+ useEffect(() => {
1620
651
  if (!debug) return;
1621
652
  import("debug-vfx").then(({ updateDebugPanel }) => {
1622
653
  if (debugValuesRef.current) {
@@ -1624,24 +655,46 @@ var VFXParticles = forwardRef(
1624
655
  }
1625
656
  });
1626
657
  }, [debug, handleDebugUpdate]);
1627
- return /* @__PURE__ */ jsx("primitive", { ref: spriteRef, object: renderObject });
658
+ return /* @__PURE__ */ jsx("primitive", { ref: spriteRef, object: system.renderObject });
659
+ }
660
+ );
661
+ var warnedWebGL = false;
662
+ var VFXParticles = forwardRef(
663
+ function VFXParticles2(_a, ref) {
664
+ var _b = _a, { fallback } = _b, props = __objRest(_b, ["fallback"]);
665
+ const { gl } = useThree();
666
+ const isWebGPU = useMemo(() => isWebGPUBackend(gl), [gl]);
667
+ if (!isWebGPU) {
668
+ if (!warnedWebGL) {
669
+ warnedWebGL = true;
670
+ console.warn(
671
+ "r3f-vfx: WebGPU backend not detected. Particle system disabled."
672
+ );
673
+ }
674
+ return /* @__PURE__ */ jsx(Fragment, { children: fallback != null ? fallback : null });
675
+ }
676
+ return /* @__PURE__ */ jsx(VFXParticlesImpl, __spreadValues({ ref }, props));
1628
677
  }
1629
678
  );
1630
679
 
1631
680
  // src/VFXEmitter.tsx
1632
681
  import {
1633
- useRef as useRef3,
1634
- useEffect as useEffect3,
682
+ useRef as useRef2,
683
+ useEffect as useEffect2,
1635
684
  useCallback as useCallback2,
685
+ useMemo as useMemo2,
1636
686
  forwardRef as forwardRef2,
1637
687
  useImperativeHandle as useImperativeHandle2
1638
688
  } from "react";
1639
- import { useFrame as useFrame2 } from "@react-three/fiber";
1640
- import { Vector3 as Vector32, Quaternion } from "three/webgpu";
689
+ import { useFrame as useFrame2, useThree as useThree2 } from "@react-three/fiber";
690
+ import { Vector3, Quaternion } from "three/webgpu";
691
+ import {
692
+ EmitterController,
693
+ isWebGPUBackend as isWebGPUBackend2
694
+ } from "core-vfx";
1641
695
  import { jsx as jsx2 } from "react/jsx-runtime";
1642
- var _worldPos = new Vector32();
1643
- var _worldQuat = new Quaternion();
1644
- var _tempVec = new Vector32();
696
+ var worldPos = new Vector3();
697
+ var worldQuat = new Quaternion();
1645
698
  var VFXEmitter = forwardRef2(function VFXEmitter2({
1646
699
  name,
1647
700
  particlesRef,
@@ -1656,51 +709,75 @@ var VFXEmitter = forwardRef2(function VFXEmitter2({
1656
709
  onEmit,
1657
710
  children
1658
711
  }, ref) {
1659
- const groupRef = useRef3(null);
1660
- const emitAccumulator = useRef3(0);
1661
- const emitting = useRef3(autoStart);
1662
- const hasEmittedOnce = useRef3(false);
712
+ const { gl } = useThree2();
713
+ const isWebGPU = useMemo2(() => isWebGPUBackend2(gl), [gl]);
714
+ const groupRef = useRef2(null);
715
+ const controllerRef = useRef2(null);
716
+ if (!controllerRef.current) {
717
+ controllerRef.current = new EmitterController({
718
+ emitCount,
719
+ delay,
720
+ autoStart,
721
+ loop,
722
+ localDirection,
723
+ direction,
724
+ overrides,
725
+ onEmit
726
+ });
727
+ }
728
+ const controller = controllerRef.current;
1663
729
  const getParticleSystem = useCallback2(() => {
1664
730
  if (particlesRef) {
1665
731
  return particlesRef.current || particlesRef;
1666
732
  }
1667
733
  return useVFXStore.getState().getParticles(name);
1668
734
  }, [name, particlesRef]);
1669
- const transformDirectionByQuat = useCallback2(
1670
- (dirRange, quat) => {
1671
- const minDir = _tempVec.set(
1672
- dirRange[0][0],
1673
- dirRange[1][0],
1674
- dirRange[2][0]
1675
- );
1676
- minDir.applyQuaternion(quat);
1677
- const maxDir = new Vector32(dirRange[0][1], dirRange[1][1], dirRange[2][1]);
1678
- maxDir.applyQuaternion(quat);
1679
- return [
1680
- [Math.min(minDir.x, maxDir.x), Math.max(minDir.x, maxDir.x)],
1681
- [Math.min(minDir.y, maxDir.y), Math.max(minDir.y, maxDir.y)],
1682
- [Math.min(minDir.z, maxDir.z), Math.max(minDir.z, maxDir.z)]
1683
- ];
1684
- },
1685
- []
1686
- );
1687
- const getEmitParams = useCallback2(() => {
1688
- if (!groupRef.current) {
1689
- return { position, direction };
1690
- }
1691
- let emitDir = direction;
1692
- groupRef.current.getWorldPosition(_worldPos);
1693
- const emitPos = [_worldPos.x, _worldPos.y, _worldPos.z];
1694
- if (localDirection && direction) {
1695
- groupRef.current.getWorldQuaternion(_worldQuat);
1696
- emitDir = transformDirectionByQuat(direction, _worldQuat);
735
+ useEffect2(() => {
736
+ if (!isWebGPU) return;
737
+ const system = getParticleSystem();
738
+ controller.setSystem(system);
739
+ }, [isWebGPU, getParticleSystem, controller]);
740
+ useEffect2(() => {
741
+ controller.updateOptions({
742
+ emitCount,
743
+ delay,
744
+ autoStart,
745
+ loop,
746
+ localDirection,
747
+ direction,
748
+ overrides,
749
+ onEmit
750
+ });
751
+ }, [
752
+ controller,
753
+ emitCount,
754
+ delay,
755
+ autoStart,
756
+ loop,
757
+ localDirection,
758
+ direction,
759
+ overrides,
760
+ onEmit
761
+ ]);
762
+ useFrame2((_, delta) => {
763
+ if (!isWebGPU) return;
764
+ if (!controller.getSystem()) {
765
+ const system = getParticleSystem();
766
+ if (system) controller.setSystem(system);
1697
767
  }
1698
- return { position: emitPos, direction: emitDir };
1699
- }, [localDirection, direction, position, transformDirectionByQuat]);
768
+ if (!groupRef.current) return;
769
+ groupRef.current.getWorldPosition(worldPos);
770
+ groupRef.current.getWorldQuaternion(worldQuat);
771
+ controller.update(delta, worldPos, worldQuat);
772
+ });
1700
773
  const emit = useCallback2(
1701
774
  (emitOverrides = null) => {
1702
- const particles = getParticleSystem();
1703
- if (!(particles == null ? void 0 : particles.spawn)) {
775
+ if (!groupRef.current) return false;
776
+ if (!controller.getSystem()) {
777
+ const system = getParticleSystem();
778
+ if (system) controller.setSystem(system);
779
+ }
780
+ if (!controller.getSystem()) {
1704
781
  if (name) {
1705
782
  console.warn(
1706
783
  `VFXEmitter: No particle system found for name "${name}"`
@@ -1708,114 +785,53 @@ var VFXEmitter = forwardRef2(function VFXEmitter2({
1708
785
  }
1709
786
  return false;
1710
787
  }
1711
- const { position: emitPos, direction: emitDir } = getEmitParams();
1712
- const [x, y, z] = emitPos;
1713
- const emitTimeDirection = emitOverrides == null ? void 0 : emitOverrides.direction;
1714
- let finalDir = emitDir;
1715
- if (emitTimeDirection && localDirection && groupRef.current) {
1716
- groupRef.current.getWorldQuaternion(_worldQuat);
1717
- finalDir = transformDirectionByQuat(emitTimeDirection, _worldQuat);
1718
- } else if (emitTimeDirection) {
1719
- finalDir = emitTimeDirection;
1720
- }
1721
- const _a = emitOverrides || {}, { direction: _ } = _a, emitOverridesWithoutDir = __objRest(_a, ["direction"]);
1722
- const mergedOverrides = __spreadValues(__spreadValues({}, overrides), emitOverridesWithoutDir);
1723
- const finalOverrides = finalDir ? __spreadProps(__spreadValues({}, mergedOverrides), { direction: finalDir }) : mergedOverrides;
1724
- particles.spawn(x, y, z, emitCount, finalOverrides);
1725
- if (onEmit) {
1726
- onEmit({ position: emitPos, count: emitCount, direction: finalDir });
1727
- }
1728
- return true;
788
+ groupRef.current.getWorldPosition(worldPos);
789
+ groupRef.current.getWorldQuaternion(worldQuat);
790
+ return controller.emitAtPosition(worldPos, worldQuat, emitOverrides);
1729
791
  },
1730
- [
1731
- getParticleSystem,
1732
- getEmitParams,
1733
- name,
1734
- emitCount,
1735
- overrides,
1736
- onEmit,
1737
- localDirection,
1738
- transformDirectionByQuat
1739
- ]
792
+ [controller, getParticleSystem, name]
1740
793
  );
1741
- useFrame2((_, delta) => {
1742
- if (!emitting.current) return;
1743
- if (!loop && hasEmittedOnce.current) {
1744
- return;
1745
- }
1746
- if (delay <= 0) {
1747
- const success = emit();
1748
- if (success) hasEmittedOnce.current = true;
1749
- } else {
1750
- emitAccumulator.current += delta;
1751
- if (emitAccumulator.current >= delay) {
1752
- emitAccumulator.current -= delay;
1753
- const success = emit();
1754
- if (success) hasEmittedOnce.current = true;
1755
- }
1756
- }
1757
- });
1758
- const start = useCallback2(() => {
1759
- emitting.current = true;
1760
- hasEmittedOnce.current = false;
1761
- emitAccumulator.current = 0;
1762
- }, []);
1763
- const stop = useCallback2(() => {
1764
- emitting.current = false;
1765
- }, []);
1766
794
  const burst = useCallback2(
1767
795
  (count) => {
1768
- const particles = getParticleSystem();
1769
- if (!(particles == null ? void 0 : particles.spawn)) return false;
1770
- const { position: emitPos, direction: emitDir } = getEmitParams();
1771
- const [x, y, z] = emitPos;
1772
- const finalOverrides = emitDir ? __spreadProps(__spreadValues({}, overrides), { direction: emitDir }) : overrides;
1773
- particles.spawn(x, y, z, count != null ? count : emitCount, finalOverrides);
1774
- if (onEmit) {
1775
- onEmit({
1776
- position: emitPos,
1777
- count: count != null ? count : emitCount,
1778
- direction: emitDir
1779
- });
796
+ if (!groupRef.current) return false;
797
+ if (!controller.getSystem()) {
798
+ const system = getParticleSystem();
799
+ if (system) controller.setSystem(system);
1780
800
  }
1781
- return true;
801
+ if (!controller.getSystem()) return false;
802
+ groupRef.current.getWorldPosition(worldPos);
803
+ groupRef.current.getWorldQuaternion(worldQuat);
804
+ return controller.burst(count, worldPos, worldQuat);
1782
805
  },
1783
- [getParticleSystem, getEmitParams, emitCount, overrides, onEmit]
806
+ [controller, getParticleSystem]
1784
807
  );
1785
- useEffect3(() => {
1786
- emitting.current = autoStart;
1787
- if (autoStart) {
1788
- hasEmittedOnce.current = false;
1789
- emitAccumulator.current = 0;
1790
- }
1791
- }, [autoStart]);
808
+ const start = useCallback2(() => controller.start(), [controller]);
809
+ const stop = useCallback2(() => controller.stop(), [controller]);
1792
810
  useImperativeHandle2(
1793
811
  ref,
1794
812
  () => ({
1795
- /** Emit particles at current position */
1796
813
  emit,
1797
- /** Burst emit - emit immediately regardless of autoStart */
1798
814
  burst,
1799
- /** Start auto-emission */
1800
815
  start,
1801
- /** Stop auto-emission */
1802
816
  stop,
1803
- /** Check if currently emitting */
1804
817
  get isEmitting() {
1805
- return emitting.current;
818
+ return controller.isEmitting;
1806
819
  },
1807
- /** Get the linked particle system */
1808
820
  getParticleSystem,
1809
- /** Get the group ref for direct access */
1810
821
  get group() {
1811
822
  return groupRef.current;
1812
823
  }
1813
824
  }),
1814
- [emit, burst, start, stop, getParticleSystem]
825
+ [emit, burst, start, stop, controller, getParticleSystem]
826
+ );
827
+ return (
828
+ // @ts-expect-error
829
+ /* @__PURE__ */ jsx2("group", { ref: groupRef, position, children })
1815
830
  );
1816
- return /* @__PURE__ */ jsx2("group", { ref: groupRef, position, children });
1817
831
  });
1818
832
  function useVFXEmitter(name) {
833
+ const { gl } = useThree2();
834
+ const isWebGPU = useMemo2(() => isWebGPUBackend2(gl), [gl]);
1819
835
  const getParticles = useVFXStore((s) => s.getParticles);
1820
836
  const storeEmit = useVFXStore((s) => s.emit);
1821
837
  const storeStart = useVFXStore((s) => s.start);
@@ -1823,31 +839,44 @@ function useVFXEmitter(name) {
1823
839
  const storeClear = useVFXStore((s) => s.clear);
1824
840
  const emit = useCallback2(
1825
841
  (position = [0, 0, 0], count = 20, overrides = null) => {
842
+ if (!isWebGPU) return false;
1826
843
  const [x, y, z] = position;
1827
844
  return storeEmit(name, { x, y, z, count, overrides });
1828
845
  },
1829
- [name, storeEmit]
846
+ [isWebGPU, name, storeEmit]
1830
847
  );
1831
848
  const burst = useCallback2(
1832
849
  (position = [0, 0, 0], count = 50, overrides = null) => {
850
+ if (!isWebGPU) return false;
1833
851
  const [x, y, z] = position;
1834
852
  return storeEmit(name, { x, y, z, count, overrides });
1835
853
  },
1836
- [name, storeEmit]
854
+ [isWebGPU, name, storeEmit]
1837
855
  );
1838
- const start = useCallback2(() => storeStart(name), [name, storeStart]);
1839
- const stop = useCallback2(() => storeStop(name), [name, storeStop]);
1840
- const clear = useCallback2(() => storeClear(name), [name, storeClear]);
856
+ const start = useCallback2(() => {
857
+ if (!isWebGPU) return false;
858
+ return storeStart(name);
859
+ }, [isWebGPU, name, storeStart]);
860
+ const stop = useCallback2(() => {
861
+ if (!isWebGPU) return false;
862
+ return storeStop(name);
863
+ }, [isWebGPU, name, storeStop]);
864
+ const clear = useCallback2(() => {
865
+ if (!isWebGPU) return false;
866
+ return storeClear(name);
867
+ }, [isWebGPU, name, storeClear]);
1841
868
  const isEmitting = useCallback2(() => {
1842
869
  var _a;
870
+ if (!isWebGPU) return false;
1843
871
  const particles = getParticles(name);
1844
872
  return (_a = particles == null ? void 0 : particles.isEmitting) != null ? _a : false;
1845
- }, [name, getParticles]);
873
+ }, [isWebGPU, name, getParticles]);
1846
874
  const getUniforms = useCallback2(() => {
1847
875
  var _a;
876
+ if (!isWebGPU) return null;
1848
877
  const particles = getParticles(name);
1849
878
  return (_a = particles == null ? void 0 : particles.uniforms) != null ? _a : null;
1850
- }, [name, getParticles]);
879
+ }, [isWebGPU, name, getParticles]);
1851
880
  return {
1852
881
  emit,
1853
882
  burst,
@@ -1856,23 +885,38 @@ function useVFXEmitter(name) {
1856
885
  clear,
1857
886
  isEmitting,
1858
887
  getUniforms,
1859
- getParticles: () => getParticles(name)
888
+ getParticles: () => isWebGPU ? getParticles(name) : null
1860
889
  };
1861
890
  }
891
+
892
+ // src/index.ts
893
+ import {
894
+ VFXParticleSystem as VFXParticleSystem2,
895
+ EmitterController as EmitterController2,
896
+ isWebGPUBackend as isWebGPUBackend3,
897
+ isNonDefaultRotation as isNonDefaultRotation2,
898
+ normalizeProps as normalizeProps2,
899
+ resolveCurveTexture
900
+ } from "core-vfx";
1862
901
  export {
1863
902
  Appearance2 as Appearance,
1864
- AttractorType2 as AttractorType,
903
+ AttractorType,
1865
904
  Blending2 as Blending,
1866
- CurveChannel2 as CurveChannel,
1867
- Easing2 as Easing,
905
+ CurveChannel,
906
+ Easing,
907
+ EmitterController2 as EmitterController,
1868
908
  EmitterShape2 as EmitterShape,
1869
909
  Lighting2 as Lighting,
1870
910
  VFXEmitter,
911
+ VFXParticleSystem2 as VFXParticleSystem,
1871
912
  VFXParticles,
1872
913
  bakeCurveToArray,
1873
914
  buildCurveTextureBin,
1874
- createCombinedCurveTexture3 as createCombinedCurveTexture,
1875
- useCurveTextureAsync,
915
+ createCombinedCurveTexture,
916
+ isNonDefaultRotation2 as isNonDefaultRotation,
917
+ isWebGPUBackend3 as isWebGPUBackend,
918
+ normalizeProps2 as normalizeProps,
919
+ resolveCurveTexture,
1876
920
  useVFXEmitter,
1877
921
  useVFXStore
1878
922
  };