core-vfx 0.0.9 → 0.0.10
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +471 -471
- package/package.json +1 -1
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
|