r3f-vfx 0.1.8 → 0.1.9

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (3) hide show
  1. package/README.md +471 -471
  2. package/dist/index.js +4 -1
  3. package/package.json +3 -3
package/README.md CHANGED
@@ -1,471 +1,471 @@
1
- # r3f-vfx
2
-
3
- High-performance GPU-accelerated particle system for Three.js WebGPU with React Three Fiber.
4
-
5
- ## Features
6
-
7
- - 🚀 **GPU Compute Shaders** - All particle simulation runs on the GPU for maximum performance
8
- - 🎨 **Flexible Appearance** - Sprites, custom geometry, materials, and shaders
9
- - 🌀 **Advanced Physics** - Gravity, turbulence, attractors, collisions, and more
10
- - 🎯 **Multiple Emitter Shapes** - Point, Box, Sphere, Cone, Disk, and Edge emitters
11
- - 📊 **Curve-based Control** - Bezier curves for size, opacity, velocity, and rotation over lifetime
12
- - 🔗 **Emitter System** - Decoupled emitters that can share particle systems
13
- - ⚡ **WebGPU Native** - Built specifically for Three.js WebGPU renderer
14
-
15
- ## Installation
16
-
17
- ```bash
18
- npm install r3f-vfx
19
- ```
20
-
21
- ### Peer Dependencies
22
-
23
- ```bash
24
- npm install three @react-three/fiber react
25
- ```
26
-
27
- ## Quick Start
28
-
29
- ```tsx
30
- import { Canvas } from '@react-three/fiber'
31
- import { VFXParticles, Appearance, EmitterShape } from 'r3f-vfx'
32
- import * as THREE from 'three/webgpu'
33
-
34
- function App() {
35
- return (
36
- <Canvas>
37
- <VFXParticles debug />
38
- </Canvas>
39
- )
40
- }
41
- ```
42
-
43
- That's it, start designing in the debug panel, then copy JSX
44
-
45
- ## API Reference
46
-
47
- ### VFXParticles
48
-
49
- The main particle system component.
50
-
51
- #### Basic Props
52
-
53
- | Prop | Type | Default | Description |
54
- | -------------- | ----------- | ----------- | ------------------------------------------- |
55
- | `name` | `string` | - | Register system for use with VFXEmitter |
56
- | `maxParticles` | `number` | `10000` | Maximum number of particles |
57
- | `autoStart` | `boolean` | `true` | Start emitting automatically |
58
- | `delay` | `number` | `0` | Seconds between emissions (0 = every frame) |
59
- | `emitCount` | `number` | `1` | Particles to emit per burst |
60
- | `position` | `[x, y, z]` | `[0, 0, 0]` | Emitter position |
61
-
62
- #### Appearance Props
63
-
64
- | Prop | Type | Default | Description |
65
- | ------------- | ------------------------ | ------------- | ----------------------------------------------------------- |
66
- | `size` | `number \| [min, max]` | `[0.1, 0.3]` | Particle size range |
67
- | `colorStart` | `string[]` | `["#ffffff"]` | Starting colors (random pick) |
68
- | `colorEnd` | `string[] \| null` | `null` | Ending colors (null = no transition) |
69
- | `fadeSize` | `number \| [start, end]` | `[1, 0]` | Size multiplier over lifetime |
70
- | `fadeOpacity` | `number \| [start, end]` | `[1, 0]` | Opacity over lifetime |
71
- | `appearance` | `Appearance` | `GRADIENT` | Shape: `DEFAULT`, `GRADIENT`, `CIRCULAR` |
72
- | `intensity` | `number` | `1` | Color intensity multiplier |
73
- | `blending` | `Blending` | `NORMAL` | Blend mode: `NORMAL`, `ADDITIVE`, `MULTIPLY`, `SUBTRACTIVE` |
74
-
75
- #### Physics Props
76
-
77
- | Prop | Type | Default | Description |
78
- | ----------- | ----------------------- | ------------------------- | ---------------------------- |
79
- | `lifetime` | `number \| [min, max]` | `[1, 2]` | Particle lifetime in seconds |
80
- | `speed` | `number \| [min, max]` | `[0.1, 0.1]` | Initial speed |
81
- | `direction` | `Range3D \| [min, max]` | `[[-1,1], [0,1], [-1,1]]` | Emission direction per axis |
82
- | `gravity` | `[x, y, z]` | `[0, 0, 0]` | Gravity vector |
83
- | `friction` | `FrictionConfig` | `{ intensity: 0 }` | Velocity damping |
84
-
85
- #### Emitter Shape Props
86
-
87
- | Prop | Type | Default | Description |
88
- | -------------------- | ---------------- | ----------------------- | ------------------------------------------------------- |
89
- | `emitterShape` | `EmitterShape` | `BOX` | Shape: `POINT`, `BOX`, `SPHERE`, `CONE`, `DISK`, `EDGE` |
90
- | `emitterRadius` | `[inner, outer]` | `[0, 1]` | Radius range for sphere/cone/disk |
91
- | `emitterAngle` | `number` | `π/4` | Cone angle in radians |
92
- | `emitterHeight` | `[min, max]` | `[0, 1]` | Height range for cone |
93
- | `emitterDirection` | `[x, y, z]` | `[0, 1, 0]` | Cone/disk normal direction |
94
- | `emitterSurfaceOnly` | `boolean` | `false` | Emit from surface only |
95
- | `startPosition` | `Range3D` | `[[0,0], [0,0], [0,0]]` | Position offset per axis |
96
-
97
- #### Geometry Mode Props
98
-
99
- | Prop | Type | Default | Description |
100
- | ------------------- | ----------------------- | ---------- | ---------------------------------------------------------- |
101
- | `geometry` | `BufferGeometry` | `null` | Custom particle geometry |
102
- | `lighting` | `Lighting` | `STANDARD` | Material: `BASIC`, `STANDARD`, `PHYSICAL` |
103
- | `shadow` | `boolean` | `false` | Enable shadow casting/receiving |
104
- | `orientToDirection` | `boolean` | `false` | Orient geometry to velocity |
105
- | `orientAxis` | `string` | `"z"` | Axis to align: `"x"`, `"y"`, `"z"`, `"-x"`, `"-y"`, `"-z"` |
106
- | `rotation` | `Range3D \| [min, max]` | `[0, 0]` | Initial rotation per axis |
107
- | `rotationSpeed` | `Range3D \| [min, max]` | `[0, 0]` | Rotation speed rad/s |
108
-
109
- #### Stretch Props
110
-
111
- | Prop | Type | Default | Description |
112
- | ---------------- | --------------- | ------- | ----------------------------- |
113
- | `stretchBySpeed` | `StretchConfig` | `null` | Stretch particles by velocity |
114
-
115
- ```ts
116
- interface StretchConfig {
117
- factor: number // Stretch multiplier
118
- maxStretch: number // Maximum stretch amount
119
- }
120
- ```
121
-
122
- #### Turbulence Props
123
-
124
- | Prop | Type | Default | Description |
125
- | ------------ | ------------------ | ------- | --------------------- |
126
- | `turbulence` | `TurbulenceConfig` | `null` | Curl noise turbulence |
127
-
128
- ```ts
129
- interface TurbulenceConfig {
130
- intensity: number // Turbulence strength
131
- frequency: number // Noise scale
132
- speed: number // Animation speed
133
- }
134
- ```
135
-
136
- #### Attractor Props
137
-
138
- | Prop | Type | Default | Description |
139
- | ----------------- | ------------------- | ------- | -------------------------------- |
140
- | `attractors` | `AttractorConfig[]` | `null` | Up to 4 attractors |
141
- | `attractToCenter` | `boolean` | `false` | Pull particles to emitter center |
142
-
143
- ```ts
144
- interface AttractorConfig {
145
- position: [x, y, z]
146
- strength: number // Positive = attract, negative = repel
147
- radius?: number // 0 = infinite range
148
- type?: 'point' | 'vortex'
149
- axis?: [x, y, z] // Vortex rotation axis
150
- }
151
- ```
152
-
153
- #### Collision Props
154
-
155
- | Prop | Type | Default | Description |
156
- | ----------- | ----------------- | ------- | --------------- |
157
- | `collision` | `CollisionConfig` | `null` | Plane collision |
158
-
159
- ```ts
160
- interface CollisionConfig {
161
- plane: { y: number } // Plane Y position
162
- bounce?: number // Bounce factor (0-1)
163
- friction?: number // Horizontal friction
164
- die?: boolean // Kill on collision
165
- sizeBasedGravity?: number // Gravity multiplier by size
166
- }
167
- ```
168
-
169
- #### Soft Particles Props
170
-
171
- | Prop | Type | Default | Description |
172
- | --------------- | --------- | ------- | ---------------------------- |
173
- | `softParticles` | `boolean` | `false` | Fade near geometry |
174
- | `softDistance` | `number` | `0.5` | Fade distance in world units |
175
-
176
- #### Curve Props
177
-
178
- All curves use Bezier spline format:
179
-
180
- ```ts
181
- interface CurveData {
182
- points: Array<{
183
- pos: [x, y] // Position (x: 0-1 progress, y: value)
184
- handleIn?: [x, y] // Bezier handle in (offset)
185
- handleOut?: [x, y] // Bezier handle out (offset)
186
- }>
187
- }
188
- ```
189
-
190
- | Prop | Type | Description |
191
- | -------------------- | ----------- | ---------------------------------------- |
192
- | `fadeSizeCurve` | `CurveData` | Size multiplier over lifetime |
193
- | `fadeOpacityCurve` | `CurveData` | Opacity over lifetime |
194
- | `velocityCurve` | `CurveData` | Velocity multiplier (overrides friction) |
195
- | `rotationSpeedCurve` | `CurveData` | Rotation speed multiplier |
196
-
197
- #### Custom Shader Props
198
-
199
- | Prop | Type | Description |
200
- | ---------------- | -------------- | ------------------------------ |
201
- | `colorNode` | `NodeFunction` | Custom color shader |
202
- | `opacityNode` | `NodeFunction` | Custom opacity shader |
203
- | `backdropNode` | `NodeFunction` | Backdrop sampling (refraction) |
204
- | `castShadowNode` | `NodeFunction` | Shadow map output |
205
- | `alphaTestNode` | `NodeFunction` | Alpha test/discard |
206
-
207
- ```ts
208
- type NodeFunction = (data: ParticleData, defaultColor?: Node) => Node
209
-
210
- interface ParticleData {
211
- progress: Node // 0 → 1 over lifetime
212
- lifetime: Node // 1 → 0 over lifetime
213
- position: Node // vec3 world position
214
- velocity: Node // vec3 velocity
215
- size: Node // float size
216
- rotation: Node // vec3 rotation
217
- colorStart: Node // vec3 start color
218
- colorEnd: Node // vec3 end color
219
- color: Node // vec3 interpolated color
220
- intensifiedColor: Node // color × intensity
221
- shapeMask: Node // float alpha mask
222
- index: Node // particle index
223
- }
224
- ```
225
-
226
- #### Texture Props
227
-
228
- | Prop | Type | Description |
229
- | ---------- | ---------------- | ------------------- |
230
- | `alphaMap` | `Texture` | Alpha/shape texture |
231
- | `flipbook` | `FlipbookConfig` | Animated flipbook |
232
-
233
- ```ts
234
- interface FlipbookConfig {
235
- rows: number
236
- columns: number
237
- }
238
- ```
239
-
240
- ### VFXEmitter
241
-
242
- Decoupled emitter component that links to a VFXParticles system.
243
-
244
- ```tsx
245
- <VFXParticles name="sparks" maxParticles={1000} autoStart={false} />
246
-
247
- <group ref={playerRef}>
248
- <VFXEmitter
249
- name="sparks"
250
- position={[0, 1, 0]}
251
- emitCount={5}
252
- delay={0.1}
253
- direction={[[0, 0], [0, 0], [-1, -1]]}
254
- localDirection={true}
255
- />
256
- </group>
257
- ```
258
-
259
- #### Props
260
-
261
- | Prop | Type | Default | Description |
262
- | ---------------- | ------------------ | ----------- | -------------------------------------- |
263
- | `name` | `string` | - | Name of VFXParticles system |
264
- | `particlesRef` | `Ref<ParticleAPI>` | - | Direct ref (alternative to name) |
265
- | `position` | `[x, y, z]` | `[0, 0, 0]` | Local position offset |
266
- | `emitCount` | `number` | `10` | Particles per burst |
267
- | `delay` | `number` | `0` | Seconds between emissions |
268
- | `autoStart` | `boolean` | `true` | Start emitting automatically |
269
- | `loop` | `boolean` | `true` | Keep emitting (false = once) |
270
- | `localDirection` | `boolean` | `false` | Transform direction by parent rotation |
271
- | `direction` | `Range3D` | - | Direction override |
272
- | `overrides` | `SpawnOverrides` | - | Per-spawn property overrides |
273
- | `onEmit` | `function` | - | Callback after each emission |
274
-
275
- #### Ref Methods
276
-
277
- ```ts
278
- interface VFXEmitterAPI {
279
- emit(): boolean // Emit at current position
280
- burst(count?: number): boolean // Burst emit
281
- start(): void // Start auto-emission
282
- stop(): void // Stop auto-emission
283
- isEmitting: boolean // Current state
284
- getParticleSystem(): ParticleAPI
285
- group: THREE.Group // The group element
286
- }
287
- ```
288
-
289
- ### useVFXEmitter Hook
290
-
291
- Programmatic emitter control.
292
-
293
- ```tsx
294
- function MyComponent() {
295
- const { emit, burst, start, stop } = useVFXEmitter('sparks')
296
-
297
- const handleClick = () => {
298
- burst([0, 1, 0], 100, { colorStart: ['#ff0000'] })
299
- }
300
-
301
- return <mesh onClick={handleClick}>...</mesh>
302
- }
303
- ```
304
-
305
- #### Returns
306
-
307
- ```ts
308
- interface UseVFXEmitterResult {
309
- emit(
310
- position?: [x, y, z],
311
- count?: number,
312
- overrides?: SpawnOverrides
313
- ): boolean
314
- burst(
315
- position?: [x, y, z],
316
- count?: number,
317
- overrides?: SpawnOverrides
318
- ): boolean
319
- start(): boolean
320
- stop(): boolean
321
- clear(): boolean
322
- isEmitting(): boolean
323
- getUniforms(): Record<string, { value: unknown }>
324
- getParticles(): ParticleAPI
325
- }
326
- ```
327
-
328
- ### useVFXStore
329
-
330
- Zustand store for managing particle systems.
331
-
332
- ```ts
333
- const store = useVFXStore()
334
-
335
- // Access registered particle systems
336
- const sparks = store.getParticles('sparks')
337
- sparks?.spawn(0, 0, 0, 50)
338
-
339
- // Store methods
340
- store.emit('sparks', { x: 0, y: 0, z: 0, count: 20 })
341
- store.start('sparks')
342
- store.stop('sparks')
343
- store.clear('sparks')
344
- ```
345
-
346
- ## Examples
347
-
348
- ### Fire Effect
349
-
350
- ```tsx
351
- <VFXParticles
352
- maxParticles={3000}
353
- size={[0.3, 0.8]}
354
- colorStart={['#ff6600', '#ffcc00', '#ff0000']}
355
- colorEnd={['#ff0000', '#330000']}
356
- fadeSize={[1, 0.2]}
357
- fadeOpacity={[1, 0]}
358
- gravity={[0, 0.5, 0]}
359
- lifetime={[0.4, 0.8]}
360
- direction={[
361
- [-0.3, 0.3],
362
- [0.5, 1],
363
- [-0.3, 0.3],
364
- ]}
365
- speed={[0.01, 0.05]}
366
- friction={{ intensity: 0.03, easing: 'easeOut' }}
367
- appearance={Appearance.GRADIENT}
368
- intensity={10}
369
- />
370
- ```
371
-
372
- ### Sphere Burst
373
-
374
- ```tsx
375
- <VFXParticles
376
- maxParticles={500}
377
- size={[0.05, 0.1]}
378
- colorStart={['#00ffff', '#0088ff']}
379
- fadeOpacity={[1, 0]}
380
- lifetime={[1, 2]}
381
- emitterShape={EmitterShape.SPHERE}
382
- emitterRadius={[0.5, 1]}
383
- startPositionAsDirection={true}
384
- speed={[0.1, 0.2]}
385
- />
386
- ```
387
-
388
- ### 3D Geometry Particles
389
-
390
- ```tsx
391
- import { BoxGeometry } from 'three/webgpu'
392
- ;<VFXParticles
393
- geometry={new BoxGeometry(1, 1, 1)}
394
- maxParticles={500}
395
- size={[0.1, 0.2]}
396
- colorStart={['#ff00ff', '#aa00ff']}
397
- gravity={[0, -2, 0]}
398
- lifetime={[1, 2]}
399
- rotation={[
400
- [0, Math.PI * 2],
401
- [0, Math.PI * 2],
402
- [0, Math.PI * 2],
403
- ]}
404
- shadow={true}
405
- lighting={Lighting.STANDARD}
406
- />
407
- ```
408
-
409
- ### Turbulent Smoke
410
-
411
- ```tsx
412
- <VFXParticles
413
- maxParticles={300}
414
- size={[0.3, 0.6]}
415
- colorStart={['#666666', '#888888']}
416
- colorEnd={['#333333']}
417
- fadeSize={[0.5, 1.5]}
418
- fadeOpacity={[0.6, 0]}
419
- gravity={[0, 0.5, 0]}
420
- lifetime={[3, 5]}
421
- direction={[
422
- [-0.1, 0.1],
423
- [0.3, 0.5],
424
- [-0.1, 0.1],
425
- ]}
426
- speed={[0.02, 0.05]}
427
- turbulence={{
428
- intensity: 1.2,
429
- frequency: 0.8,
430
- speed: 0.3,
431
- }}
432
- />
433
- ```
434
-
435
- ### Velocity Curves
436
-
437
- ```tsx
438
- <VFXParticles
439
- maxParticles={1000}
440
- velocityCurve={{
441
- points: [
442
- { pos: [0, 1], handleOut: [0.1, 0] },
443
- { pos: [0.5, 0.2], handleIn: [-0.1, 0], handleOut: [0.1, 0] },
444
- { pos: [1, 0], handleIn: [-0.1, 0] },
445
- ],
446
- }}
447
- speed={[0.5, 1]}
448
- lifetime={[2, 3]}
449
- />
450
- ```
451
-
452
- ## TypeScript
453
-
454
- Full TypeScript support with exported types:
455
-
456
- ```ts
457
- import type {
458
- VFXParticlesProps,
459
- VFXEmitterProps,
460
- ParticleAPI,
461
- SpawnOverrides,
462
- CurveData,
463
- TurbulenceConfig,
464
- CollisionConfig,
465
- AttractorConfig,
466
- } from 'r3f-vfx'
467
- ```
468
-
469
- ## License
470
-
471
- MIT
1
+ # r3f-vfx
2
+
3
+ High-performance GPU-accelerated particle system for Three.js WebGPU with React Three Fiber.
4
+
5
+ ## Features
6
+
7
+ - 🚀 **GPU Compute Shaders** - All particle simulation runs on the GPU for maximum performance
8
+ - 🎨 **Flexible Appearance** - Sprites, custom geometry, materials, and shaders
9
+ - 🌀 **Advanced Physics** - Gravity, turbulence, attractors, collisions, and more
10
+ - 🎯 **Multiple Emitter Shapes** - Point, Box, Sphere, Cone, Disk, and Edge emitters
11
+ - 📊 **Curve-based Control** - Bezier curves for size, opacity, velocity, and rotation over lifetime
12
+ - 🔗 **Emitter System** - Decoupled emitters that can share particle systems
13
+ - ⚡ **WebGPU Native** - Built specifically for Three.js WebGPU renderer
14
+
15
+ ## Installation
16
+
17
+ ```bash
18
+ npm install r3f-vfx
19
+ ```
20
+
21
+ ### Peer Dependencies
22
+
23
+ ```bash
24
+ npm install three @react-three/fiber react
25
+ ```
26
+
27
+ ## Quick Start
28
+
29
+ ```tsx
30
+ import { Canvas } from '@react-three/fiber'
31
+ import { VFXParticles, Appearance, EmitterShape } from 'r3f-vfx'
32
+ import * as THREE from 'three/webgpu'
33
+
34
+ function App() {
35
+ return (
36
+ <Canvas>
37
+ <VFXParticles debug />
38
+ </Canvas>
39
+ )
40
+ }
41
+ ```
42
+
43
+ That's it, start designing in the debug panel, then copy JSX
44
+
45
+ ## API Reference
46
+
47
+ ### VFXParticles
48
+
49
+ The main particle system component.
50
+
51
+ #### Basic Props
52
+
53
+ | Prop | Type | Default | Description |
54
+ | -------------- | ----------- | ----------- | ------------------------------------------- |
55
+ | `name` | `string` | - | Register system for use with VFXEmitter |
56
+ | `maxParticles` | `number` | `10000` | Maximum number of particles |
57
+ | `autoStart` | `boolean` | `true` | Start emitting automatically |
58
+ | `delay` | `number` | `0` | Seconds between emissions (0 = every frame) |
59
+ | `emitCount` | `number` | `1` | Particles to emit per burst |
60
+ | `position` | `[x, y, z]` | `[0, 0, 0]` | Emitter position |
61
+
62
+ #### Appearance Props
63
+
64
+ | Prop | Type | Default | Description |
65
+ | ------------- | ------------------------ | ------------- | ----------------------------------------------------------- |
66
+ | `size` | `number \| [min, max]` | `[0.1, 0.3]` | Particle size range |
67
+ | `colorStart` | `string[]` | `["#ffffff"]` | Starting colors (random pick) |
68
+ | `colorEnd` | `string[] \| null` | `null` | Ending colors (null = no transition) |
69
+ | `fadeSize` | `number \| [start, end]` | `[1, 0]` | Size multiplier over lifetime |
70
+ | `fadeOpacity` | `number \| [start, end]` | `[1, 0]` | Opacity over lifetime |
71
+ | `appearance` | `Appearance` | `GRADIENT` | Shape: `DEFAULT`, `GRADIENT`, `CIRCULAR` |
72
+ | `intensity` | `number` | `1` | Color intensity multiplier |
73
+ | `blending` | `Blending` | `NORMAL` | Blend mode: `NORMAL`, `ADDITIVE`, `MULTIPLY`, `SUBTRACTIVE` |
74
+
75
+ #### Physics Props
76
+
77
+ | Prop | Type | Default | Description |
78
+ | ----------- | ----------------------- | ------------------------- | ---------------------------- |
79
+ | `lifetime` | `number \| [min, max]` | `[1, 2]` | Particle lifetime in seconds |
80
+ | `speed` | `number \| [min, max]` | `[0.1, 0.1]` | Initial speed |
81
+ | `direction` | `Range3D \| [min, max]` | `[[-1,1], [0,1], [-1,1]]` | Emission direction per axis |
82
+ | `gravity` | `[x, y, z]` | `[0, 0, 0]` | Gravity vector |
83
+ | `friction` | `FrictionConfig` | `{ intensity: 0 }` | Velocity damping |
84
+
85
+ #### Emitter Shape Props
86
+
87
+ | Prop | Type | Default | Description |
88
+ | -------------------- | ---------------- | ----------------------- | ------------------------------------------------------- |
89
+ | `emitterShape` | `EmitterShape` | `BOX` | Shape: `POINT`, `BOX`, `SPHERE`, `CONE`, `DISK`, `EDGE` |
90
+ | `emitterRadius` | `[inner, outer]` | `[0, 1]` | Radius range for sphere/cone/disk |
91
+ | `emitterAngle` | `number` | `π/4` | Cone angle in radians |
92
+ | `emitterHeight` | `[min, max]` | `[0, 1]` | Height range for cone |
93
+ | `emitterDirection` | `[x, y, z]` | `[0, 1, 0]` | Cone/disk normal direction |
94
+ | `emitterSurfaceOnly` | `boolean` | `false` | Emit from surface only |
95
+ | `startPosition` | `Range3D` | `[[0,0], [0,0], [0,0]]` | Position offset per axis |
96
+
97
+ #### Geometry Mode Props
98
+
99
+ | Prop | Type | Default | Description |
100
+ | ------------------- | ----------------------- | ---------- | ---------------------------------------------------------- |
101
+ | `geometry` | `BufferGeometry` | `null` | Custom particle geometry |
102
+ | `lighting` | `Lighting` | `STANDARD` | Material: `BASIC`, `STANDARD`, `PHYSICAL` |
103
+ | `shadow` | `boolean` | `false` | Enable shadow casting/receiving |
104
+ | `orientToDirection` | `boolean` | `false` | Orient geometry to velocity |
105
+ | `orientAxis` | `string` | `"z"` | Axis to align: `"x"`, `"y"`, `"z"`, `"-x"`, `"-y"`, `"-z"` |
106
+ | `rotation` | `Range3D \| [min, max]` | `[0, 0]` | Initial rotation per axis |
107
+ | `rotationSpeed` | `Range3D \| [min, max]` | `[0, 0]` | Rotation speed rad/s |
108
+
109
+ #### Stretch Props
110
+
111
+ | Prop | Type | Default | Description |
112
+ | ---------------- | --------------- | ------- | ----------------------------- |
113
+ | `stretchBySpeed` | `StretchConfig` | `null` | Stretch particles by velocity |
114
+
115
+ ```ts
116
+ interface StretchConfig {
117
+ factor: number // Stretch multiplier
118
+ maxStretch: number // Maximum stretch amount
119
+ }
120
+ ```
121
+
122
+ #### Turbulence Props
123
+
124
+ | Prop | Type | Default | Description |
125
+ | ------------ | ------------------ | ------- | --------------------- |
126
+ | `turbulence` | `TurbulenceConfig` | `null` | Curl noise turbulence |
127
+
128
+ ```ts
129
+ interface TurbulenceConfig {
130
+ intensity: number // Turbulence strength
131
+ frequency: number // Noise scale
132
+ speed: number // Animation speed
133
+ }
134
+ ```
135
+
136
+ #### Attractor Props
137
+
138
+ | Prop | Type | Default | Description |
139
+ | ----------------- | ------------------- | ------- | -------------------------------- |
140
+ | `attractors` | `AttractorConfig[]` | `null` | Up to 4 attractors |
141
+ | `attractToCenter` | `boolean` | `false` | Pull particles to emitter center |
142
+
143
+ ```ts
144
+ interface AttractorConfig {
145
+ position: [x, y, z]
146
+ strength: number // Positive = attract, negative = repel
147
+ radius?: number // 0 = infinite range
148
+ type?: 'point' | 'vortex'
149
+ axis?: [x, y, z] // Vortex rotation axis
150
+ }
151
+ ```
152
+
153
+ #### Collision Props
154
+
155
+ | Prop | Type | Default | Description |
156
+ | ----------- | ----------------- | ------- | --------------- |
157
+ | `collision` | `CollisionConfig` | `null` | Plane collision |
158
+
159
+ ```ts
160
+ interface CollisionConfig {
161
+ plane: { y: number } // Plane Y position
162
+ bounce?: number // Bounce factor (0-1)
163
+ friction?: number // Horizontal friction
164
+ die?: boolean // Kill on collision
165
+ sizeBasedGravity?: number // Gravity multiplier by size
166
+ }
167
+ ```
168
+
169
+ #### Soft Particles Props
170
+
171
+ | Prop | Type | Default | Description |
172
+ | --------------- | --------- | ------- | ---------------------------- |
173
+ | `softParticles` | `boolean` | `false` | Fade near geometry |
174
+ | `softDistance` | `number` | `0.5` | Fade distance in world units |
175
+
176
+ #### Curve Props
177
+
178
+ All curves use Bezier spline format:
179
+
180
+ ```ts
181
+ interface CurveData {
182
+ points: Array<{
183
+ pos: [x, y] // Position (x: 0-1 progress, y: value)
184
+ handleIn?: [x, y] // Bezier handle in (offset)
185
+ handleOut?: [x, y] // Bezier handle out (offset)
186
+ }>
187
+ }
188
+ ```
189
+
190
+ | Prop | Type | Description |
191
+ | -------------------- | ----------- | ---------------------------------------- |
192
+ | `fadeSizeCurve` | `CurveData` | Size multiplier over lifetime |
193
+ | `fadeOpacityCurve` | `CurveData` | Opacity over lifetime |
194
+ | `velocityCurve` | `CurveData` | Velocity multiplier (overrides friction) |
195
+ | `rotationSpeedCurve` | `CurveData` | Rotation speed multiplier |
196
+
197
+ #### Custom Shader Props
198
+
199
+ | Prop | Type | Description |
200
+ | ---------------- | -------------- | ------------------------------ |
201
+ | `colorNode` | `NodeFunction` | Custom color shader |
202
+ | `opacityNode` | `NodeFunction` | Custom opacity shader |
203
+ | `backdropNode` | `NodeFunction` | Backdrop sampling (refraction) |
204
+ | `castShadowNode` | `NodeFunction` | Shadow map output |
205
+ | `alphaTestNode` | `NodeFunction` | Alpha test/discard |
206
+
207
+ ```ts
208
+ type NodeFunction = (data: ParticleData, defaultColor?: Node) => Node
209
+
210
+ interface ParticleData {
211
+ progress: Node // 0 → 1 over lifetime
212
+ lifetime: Node // 1 → 0 over lifetime
213
+ position: Node // vec3 world position
214
+ velocity: Node // vec3 velocity
215
+ size: Node // float size
216
+ rotation: Node // vec3 rotation
217
+ colorStart: Node // vec3 start color
218
+ colorEnd: Node // vec3 end color
219
+ color: Node // vec3 interpolated color
220
+ intensifiedColor: Node // color × intensity
221
+ shapeMask: Node // float alpha mask
222
+ index: Node // particle index
223
+ }
224
+ ```
225
+
226
+ #### Texture Props
227
+
228
+ | Prop | Type | Description |
229
+ | ---------- | ---------------- | ------------------- |
230
+ | `alphaMap` | `Texture` | Alpha/shape texture |
231
+ | `flipbook` | `FlipbookConfig` | Animated flipbook |
232
+
233
+ ```ts
234
+ interface FlipbookConfig {
235
+ rows: number
236
+ columns: number
237
+ }
238
+ ```
239
+
240
+ ### VFXEmitter
241
+
242
+ Decoupled emitter component that links to a VFXParticles system.
243
+
244
+ ```tsx
245
+ <VFXParticles name="sparks" maxParticles={1000} autoStart={false} />
246
+
247
+ <group ref={playerRef}>
248
+ <VFXEmitter
249
+ name="sparks"
250
+ position={[0, 1, 0]}
251
+ emitCount={5}
252
+ delay={0.1}
253
+ direction={[[0, 0], [0, 0], [-1, -1]]}
254
+ localDirection={true}
255
+ />
256
+ </group>
257
+ ```
258
+
259
+ #### Props
260
+
261
+ | Prop | Type | Default | Description |
262
+ | ---------------- | ------------------ | ----------- | -------------------------------------- |
263
+ | `name` | `string` | - | Name of VFXParticles system |
264
+ | `particlesRef` | `Ref<ParticleAPI>` | - | Direct ref (alternative to name) |
265
+ | `position` | `[x, y, z]` | `[0, 0, 0]` | Local position offset |
266
+ | `emitCount` | `number` | `10` | Particles per burst |
267
+ | `delay` | `number` | `0` | Seconds between emissions |
268
+ | `autoStart` | `boolean` | `true` | Start emitting automatically |
269
+ | `loop` | `boolean` | `true` | Keep emitting (false = once) |
270
+ | `localDirection` | `boolean` | `false` | Transform direction by parent rotation |
271
+ | `direction` | `Range3D` | - | Direction override |
272
+ | `overrides` | `SpawnOverrides` | - | Per-spawn property overrides |
273
+ | `onEmit` | `function` | - | Callback after each emission |
274
+
275
+ #### Ref Methods
276
+
277
+ ```ts
278
+ interface VFXEmitterAPI {
279
+ emit(): boolean // Emit at current position
280
+ burst(count?: number): boolean // Burst emit
281
+ start(): void // Start auto-emission
282
+ stop(): void // Stop auto-emission
283
+ isEmitting: boolean // Current state
284
+ getParticleSystem(): ParticleAPI
285
+ group: THREE.Group // The group element
286
+ }
287
+ ```
288
+
289
+ ### useVFXEmitter Hook
290
+
291
+ Programmatic emitter control.
292
+
293
+ ```tsx
294
+ function MyComponent() {
295
+ const { emit, burst, start, stop } = useVFXEmitter('sparks')
296
+
297
+ const handleClick = () => {
298
+ burst([0, 1, 0], 100, { colorStart: ['#ff0000'] })
299
+ }
300
+
301
+ return <mesh onClick={handleClick}>...</mesh>
302
+ }
303
+ ```
304
+
305
+ #### Returns
306
+
307
+ ```ts
308
+ interface UseVFXEmitterResult {
309
+ emit(
310
+ position?: [x, y, z],
311
+ count?: number,
312
+ overrides?: SpawnOverrides
313
+ ): boolean
314
+ burst(
315
+ position?: [x, y, z],
316
+ count?: number,
317
+ overrides?: SpawnOverrides
318
+ ): boolean
319
+ start(): boolean
320
+ stop(): boolean
321
+ clear(): boolean
322
+ isEmitting(): boolean
323
+ getUniforms(): Record<string, { value: unknown }>
324
+ getParticles(): ParticleAPI
325
+ }
326
+ ```
327
+
328
+ ### useVFXStore
329
+
330
+ Zustand store for managing particle systems.
331
+
332
+ ```ts
333
+ const store = useVFXStore()
334
+
335
+ // Access registered particle systems
336
+ const sparks = store.getParticles('sparks')
337
+ sparks?.spawn(0, 0, 0, 50)
338
+
339
+ // Store methods
340
+ store.emit('sparks', { x: 0, y: 0, z: 0, count: 20 })
341
+ store.start('sparks')
342
+ store.stop('sparks')
343
+ store.clear('sparks')
344
+ ```
345
+
346
+ ## Examples
347
+
348
+ ### Fire Effect
349
+
350
+ ```tsx
351
+ <VFXParticles
352
+ maxParticles={3000}
353
+ size={[0.3, 0.8]}
354
+ colorStart={['#ff6600', '#ffcc00', '#ff0000']}
355
+ colorEnd={['#ff0000', '#330000']}
356
+ fadeSize={[1, 0.2]}
357
+ fadeOpacity={[1, 0]}
358
+ gravity={[0, 0.5, 0]}
359
+ lifetime={[0.4, 0.8]}
360
+ direction={[
361
+ [-0.3, 0.3],
362
+ [0.5, 1],
363
+ [-0.3, 0.3],
364
+ ]}
365
+ speed={[0.01, 0.05]}
366
+ friction={{ intensity: 0.03, easing: 'easeOut' }}
367
+ appearance={Appearance.GRADIENT}
368
+ intensity={10}
369
+ />
370
+ ```
371
+
372
+ ### Sphere Burst
373
+
374
+ ```tsx
375
+ <VFXParticles
376
+ maxParticles={500}
377
+ size={[0.05, 0.1]}
378
+ colorStart={['#00ffff', '#0088ff']}
379
+ fadeOpacity={[1, 0]}
380
+ lifetime={[1, 2]}
381
+ emitterShape={EmitterShape.SPHERE}
382
+ emitterRadius={[0.5, 1]}
383
+ startPositionAsDirection={true}
384
+ speed={[0.1, 0.2]}
385
+ />
386
+ ```
387
+
388
+ ### 3D Geometry Particles
389
+
390
+ ```tsx
391
+ import { BoxGeometry } from 'three/webgpu'
392
+ ;<VFXParticles
393
+ geometry={new BoxGeometry(1, 1, 1)}
394
+ maxParticles={500}
395
+ size={[0.1, 0.2]}
396
+ colorStart={['#ff00ff', '#aa00ff']}
397
+ gravity={[0, -2, 0]}
398
+ lifetime={[1, 2]}
399
+ rotation={[
400
+ [0, Math.PI * 2],
401
+ [0, Math.PI * 2],
402
+ [0, Math.PI * 2],
403
+ ]}
404
+ shadow={true}
405
+ lighting={Lighting.STANDARD}
406
+ />
407
+ ```
408
+
409
+ ### Turbulent Smoke
410
+
411
+ ```tsx
412
+ <VFXParticles
413
+ maxParticles={300}
414
+ size={[0.3, 0.6]}
415
+ colorStart={['#666666', '#888888']}
416
+ colorEnd={['#333333']}
417
+ fadeSize={[0.5, 1.5]}
418
+ fadeOpacity={[0.6, 0]}
419
+ gravity={[0, 0.5, 0]}
420
+ lifetime={[3, 5]}
421
+ direction={[
422
+ [-0.1, 0.1],
423
+ [0.3, 0.5],
424
+ [-0.1, 0.1],
425
+ ]}
426
+ speed={[0.02, 0.05]}
427
+ turbulence={{
428
+ intensity: 1.2,
429
+ frequency: 0.8,
430
+ speed: 0.3,
431
+ }}
432
+ />
433
+ ```
434
+
435
+ ### Velocity Curves
436
+
437
+ ```tsx
438
+ <VFXParticles
439
+ maxParticles={1000}
440
+ velocityCurve={{
441
+ points: [
442
+ { pos: [0, 1], handleOut: [0.1, 0] },
443
+ { pos: [0.5, 0.2], handleIn: [-0.1, 0], handleOut: [0.1, 0] },
444
+ { pos: [1, 0], handleIn: [-0.1, 0] },
445
+ ],
446
+ }}
447
+ speed={[0.5, 1]}
448
+ lifetime={[2, 3]}
449
+ />
450
+ ```
451
+
452
+ ## TypeScript
453
+
454
+ Full TypeScript support with exported types:
455
+
456
+ ```ts
457
+ import type {
458
+ VFXParticlesProps,
459
+ VFXEmitterProps,
460
+ ParticleAPI,
461
+ SpawnOverrides,
462
+ CurveData,
463
+ TurbulenceConfig,
464
+ CollisionConfig,
465
+ AttractorConfig,
466
+ } from 'r3f-vfx'
467
+ ```
468
+
469
+ ## License
470
+
471
+ MIT
package/dist/index.js CHANGED
@@ -1813,7 +1813,10 @@ var VFXEmitter = forwardRef2(function VFXEmitter2({
1813
1813
  }),
1814
1814
  [emit, burst, start, stop, getParticleSystem]
1815
1815
  );
1816
- return /* @__PURE__ */ jsx2("group", { ref: groupRef, position, children });
1816
+ return (
1817
+ // @ts-expect-error
1818
+ /* @__PURE__ */ jsx2("group", { ref: groupRef, position, children })
1819
+ );
1817
1820
  });
1818
1821
  function useVFXEmitter(name) {
1819
1822
  const getParticles = useVFXStore((s) => s.getParticles);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "r3f-vfx",
3
- "version": "0.1.8",
3
+ "version": "0.1.9",
4
4
  "license": "MIT",
5
5
  "type": "module",
6
6
  "repository": {
@@ -24,8 +24,8 @@
24
24
  "prepublishOnly": "bun run copy-readme"
25
25
  },
26
26
  "dependencies": {
27
- "core-vfx": "0.0.9",
28
- "debug-vfx": "0.0.9",
27
+ "core-vfx": "0.0.10",
28
+ "debug-vfx": "0.0.10",
29
29
  "zustand": "5.0.10"
30
30
  },
31
31
  "devDependencies": {