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 CHANGED
@@ -1,6 +1,8 @@
1
- # r3f-vfx
1
+ # ✨ Three VFX ✨
2
2
 
3
- High-performance GPU-accelerated particle system for Three.js WebGPU with React Three Fiber.
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
- ## Installation
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
- ### Peer Dependencies
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 three @react-three/fiber react
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, Appearance, EmitterShape } from 'r3f-vfx'
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
- That's it, start designing in the debug panel, then copy JSX
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>