threlte-vfx 0.1.0 → 0.2.1
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 +70 -13
- package/dist/VFXEmitter.svelte +171 -0
- package/dist/VFXParticles.svelte +773 -0
- package/dist/index.d.ts +117 -1
- package/dist/index.js +824 -1
- package/package.json +21 -3
package/README.md
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
|
-
#
|
|
1
|
+
# ✨ Three VFX ✨
|
|
2
2
|
|
|
3
|
-
High-performance GPU-accelerated particle system for Three.js WebGPU
|
|
3
|
+
High-performance GPU-accelerated particle system for Three.js WebGPU.
|
|
4
|
+
|
|
5
|
+
Available for React Three Fiber (R3F), and experimentally for vanilla Three.js, TresJS (Vue), and Threlte (Svelte).
|
|
4
6
|
|
|
5
7
|
## Features
|
|
6
8
|
|
|
@@ -12,24 +14,22 @@ High-performance GPU-accelerated particle system for Three.js WebGPU with React
|
|
|
12
14
|
- 🔗 **Emitter System** - Decoupled emitters that can share particle systems
|
|
13
15
|
- ⚡ **WebGPU Native** - Built specifically for Three.js WebGPU renderer
|
|
14
16
|
|
|
15
|
-
|
|
17
|
+
⚠️ Three VFX only supports WebGPU at the moment ([79% global support](https://caniuse.com/webgpu)). A `fallback` option is available to replace the particle systems by your own fallback objects.
|
|
16
18
|
|
|
17
|
-
```bash
|
|
18
|
-
npm install r3f-vfx
|
|
19
|
-
```
|
|
20
19
|
|
|
21
|
-
|
|
20
|
+
## Quick Start
|
|
21
|
+
|
|
22
|
+
### React Three Fiber
|
|
23
|
+
|
|
24
|
+
Add it to your React Three Fiber project with:
|
|
22
25
|
|
|
23
26
|
```bash
|
|
24
|
-
npm install
|
|
27
|
+
npm install r3f-vfx
|
|
25
28
|
```
|
|
26
29
|
|
|
27
|
-
## Quick Start
|
|
28
|
-
|
|
29
30
|
```tsx
|
|
30
31
|
import { Canvas } from '@react-three/fiber'
|
|
31
|
-
import { VFXParticles
|
|
32
|
-
import * as THREE from 'three/webgpu'
|
|
32
|
+
import { VFXParticles } from 'r3f-vfx'
|
|
33
33
|
|
|
34
34
|
function App() {
|
|
35
35
|
return (
|
|
@@ -40,7 +40,64 @@ function App() {
|
|
|
40
40
|
}
|
|
41
41
|
```
|
|
42
42
|
|
|
43
|
-
|
|
43
|
+
### Vanilla Three.js (Experimental)
|
|
44
|
+
|
|
45
|
+
Add it to your vanilla Three.js project with:
|
|
46
|
+
|
|
47
|
+
```bash
|
|
48
|
+
npm install vanilla-vfx
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
```ts
|
|
52
|
+
import { VFXParticles } from 'vanilla-vfx'
|
|
53
|
+
|
|
54
|
+
const particles = new VFXParticles(renderer, { debug: true })
|
|
55
|
+
scene.add(particles.renderObject)
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
### TresJS / Vue (Experimental)
|
|
59
|
+
|
|
60
|
+
Add it to your TresJS project with:
|
|
61
|
+
|
|
62
|
+
```bash
|
|
63
|
+
npm install tres-vfx
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
```vue
|
|
67
|
+
<script setup>
|
|
68
|
+
import { TresCanvas } from '@tresjs/core'
|
|
69
|
+
import { VFXParticles } from 'tres-vfx'
|
|
70
|
+
</script>
|
|
71
|
+
|
|
72
|
+
<template>
|
|
73
|
+
<TresCanvas>
|
|
74
|
+
<VFXParticles debug />
|
|
75
|
+
</TresCanvas>
|
|
76
|
+
</template>
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
### Threlte / Svelte (Experimental)
|
|
80
|
+
|
|
81
|
+
Add it to your Threlte project with:
|
|
82
|
+
|
|
83
|
+
```bash
|
|
84
|
+
npm install threlte-vfx
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
```svelte
|
|
88
|
+
<script>
|
|
89
|
+
import { Canvas } from '@threlte/core'
|
|
90
|
+
import VFXParticles from 'threlte-vfx/VFXParticles.svelte'
|
|
91
|
+
</script>
|
|
92
|
+
|
|
93
|
+
<Canvas>
|
|
94
|
+
<VFXParticles debug />
|
|
95
|
+
</Canvas>
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
## How to use
|
|
99
|
+
|
|
100
|
+
Use the debug panel to design your effect, then copy the generated code and replace it in your code.
|
|
44
101
|
|
|
45
102
|
## API Reference
|
|
46
103
|
|
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import { T, useThrelte, useTask } from '@threlte/core'
|
|
3
|
+
import { onMount } from 'svelte'
|
|
4
|
+
import { Vector3, Quaternion, Group } from 'three/webgpu'
|
|
5
|
+
import {
|
|
6
|
+
EmitterController,
|
|
7
|
+
isWebGPUBackend,
|
|
8
|
+
coreStore,
|
|
9
|
+
type EmitterControllerOptions,
|
|
10
|
+
} from 'core-vfx'
|
|
11
|
+
|
|
12
|
+
// Reusable temp objects for transforms (avoid allocations in render loop)
|
|
13
|
+
const worldPos = new Vector3()
|
|
14
|
+
const worldQuat = new Quaternion()
|
|
15
|
+
|
|
16
|
+
let {
|
|
17
|
+
name = undefined,
|
|
18
|
+
particlesRef = undefined,
|
|
19
|
+
position = [0, 0, 0],
|
|
20
|
+
emitCount = 10,
|
|
21
|
+
delay = 0,
|
|
22
|
+
autoStart = true,
|
|
23
|
+
loop = true,
|
|
24
|
+
localDirection = false,
|
|
25
|
+
direction = undefined,
|
|
26
|
+
overrides = null,
|
|
27
|
+
onEmit = undefined,
|
|
28
|
+
children,
|
|
29
|
+
}: {
|
|
30
|
+
name?: string
|
|
31
|
+
particlesRef?: any
|
|
32
|
+
position?: [number, number, number]
|
|
33
|
+
emitCount?: number
|
|
34
|
+
delay?: number
|
|
35
|
+
autoStart?: boolean
|
|
36
|
+
loop?: boolean
|
|
37
|
+
localDirection?: boolean
|
|
38
|
+
direction?: EmitterControllerOptions['direction']
|
|
39
|
+
overrides?: Record<string, unknown> | null
|
|
40
|
+
onEmit?: EmitterControllerOptions['onEmit']
|
|
41
|
+
children?: import('svelte').Snippet
|
|
42
|
+
} = $props()
|
|
43
|
+
|
|
44
|
+
const { renderer } = useThrelte()
|
|
45
|
+
|
|
46
|
+
let groupRef: Group | null = $state(null)
|
|
47
|
+
let isWebGPU = $state(false)
|
|
48
|
+
|
|
49
|
+
const controller = new EmitterController({
|
|
50
|
+
emitCount,
|
|
51
|
+
delay,
|
|
52
|
+
autoStart,
|
|
53
|
+
loop,
|
|
54
|
+
localDirection,
|
|
55
|
+
direction,
|
|
56
|
+
overrides,
|
|
57
|
+
onEmit,
|
|
58
|
+
})
|
|
59
|
+
|
|
60
|
+
function getParticleSystem() {
|
|
61
|
+
if (particlesRef) {
|
|
62
|
+
return particlesRef.value || particlesRef
|
|
63
|
+
}
|
|
64
|
+
return name ? coreStore.getState().getParticles(name) : undefined
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// Watch option changes
|
|
68
|
+
$effect(() => {
|
|
69
|
+
const _deps = [
|
|
70
|
+
emitCount,
|
|
71
|
+
delay,
|
|
72
|
+
autoStart,
|
|
73
|
+
loop,
|
|
74
|
+
localDirection,
|
|
75
|
+
direction,
|
|
76
|
+
overrides,
|
|
77
|
+
onEmit,
|
|
78
|
+
]
|
|
79
|
+
|
|
80
|
+
controller.updateOptions({
|
|
81
|
+
emitCount,
|
|
82
|
+
delay,
|
|
83
|
+
autoStart,
|
|
84
|
+
loop,
|
|
85
|
+
localDirection,
|
|
86
|
+
direction,
|
|
87
|
+
overrides,
|
|
88
|
+
onEmit,
|
|
89
|
+
})
|
|
90
|
+
})
|
|
91
|
+
|
|
92
|
+
function checkWebGPU() {
|
|
93
|
+
if (renderer && isWebGPUBackend(renderer)) {
|
|
94
|
+
isWebGPU = true
|
|
95
|
+
const system = getParticleSystem()
|
|
96
|
+
if (system) controller.setSystem(system)
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
onMount(() => {
|
|
101
|
+
checkWebGPU()
|
|
102
|
+
})
|
|
103
|
+
|
|
104
|
+
// Frame loop
|
|
105
|
+
useTask((delta) => {
|
|
106
|
+
if (!isWebGPU) return
|
|
107
|
+
|
|
108
|
+
if (!controller.getSystem()) {
|
|
109
|
+
const system = getParticleSystem()
|
|
110
|
+
if (system) controller.setSystem(system)
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
if (!groupRef) return
|
|
114
|
+
|
|
115
|
+
groupRef.getWorldPosition(worldPos)
|
|
116
|
+
groupRef.getWorldQuaternion(worldQuat)
|
|
117
|
+
controller.update(delta, worldPos, worldQuat)
|
|
118
|
+
})
|
|
119
|
+
|
|
120
|
+
// Exposed API
|
|
121
|
+
export function emit(emitOverrides: Record<string, unknown> | null = null) {
|
|
122
|
+
if (!groupRef) return false
|
|
123
|
+
|
|
124
|
+
if (!controller.getSystem()) {
|
|
125
|
+
const system = getParticleSystem()
|
|
126
|
+
if (system) controller.setSystem(system)
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
if (!controller.getSystem()) {
|
|
130
|
+
if (name) {
|
|
131
|
+
console.warn(
|
|
132
|
+
`VFXEmitter: No particle system found for name "${name}"`
|
|
133
|
+
)
|
|
134
|
+
}
|
|
135
|
+
return false
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
groupRef.getWorldPosition(worldPos)
|
|
139
|
+
groupRef.getWorldQuaternion(worldQuat)
|
|
140
|
+
return controller.emitAtPosition(worldPos, worldQuat, emitOverrides)
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
export function burst(count: number) {
|
|
144
|
+
if (!groupRef) return false
|
|
145
|
+
|
|
146
|
+
if (!controller.getSystem()) {
|
|
147
|
+
const system = getParticleSystem()
|
|
148
|
+
if (system) controller.setSystem(system)
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
if (!controller.getSystem()) return false
|
|
152
|
+
|
|
153
|
+
groupRef.getWorldPosition(worldPos)
|
|
154
|
+
groupRef.getWorldQuaternion(worldQuat)
|
|
155
|
+
return controller.burst(count, worldPos, worldQuat)
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
export function start() {
|
|
159
|
+
controller.start()
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
export function stop() {
|
|
163
|
+
controller.stop()
|
|
164
|
+
}
|
|
165
|
+
</script>
|
|
166
|
+
|
|
167
|
+
<T.Group bind:ref={groupRef} position={position}>
|
|
168
|
+
{#if children}
|
|
169
|
+
{@render children()}
|
|
170
|
+
{/if}
|
|
171
|
+
</T.Group>
|