tres-vfx 0.2.0 → 0.3.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.
- package/README.md +68 -13
- package/dist/index.d.ts +1 -3
- package/dist/index.js +50 -77
- package/package.json +3 -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
|
|
|
@@ -11,25 +13,21 @@ High-performance GPU-accelerated particle system for Three.js WebGPU with React
|
|
|
11
13
|
- 📊 **Curve-based Control** - Bezier curves for size, opacity, velocity, and rotation over lifetime
|
|
12
14
|
- 🔗 **Emitter System** - Decoupled emitters that can share particle systems
|
|
13
15
|
- ⚡ **WebGPU Native** - Built specifically for Three.js WebGPU renderer
|
|
16
|
+
- 🐢 **WebGL fallback** – Three VFX targets WebGPU ([79% global support](https://caniuse.com/webgpu)) but provides a CPU fallback
|
|
14
17
|
|
|
15
|
-
##
|
|
18
|
+
## Quick Start
|
|
16
19
|
|
|
17
|
-
|
|
18
|
-
npm install r3f-vfx
|
|
19
|
-
```
|
|
20
|
+
### React Three Fiber
|
|
20
21
|
|
|
21
|
-
|
|
22
|
+
Add it to your React Three Fiber project with:
|
|
22
23
|
|
|
23
24
|
```bash
|
|
24
|
-
npm install
|
|
25
|
+
npm install r3f-vfx
|
|
25
26
|
```
|
|
26
27
|
|
|
27
|
-
## Quick Start
|
|
28
|
-
|
|
29
28
|
```tsx
|
|
30
29
|
import { Canvas } from '@react-three/fiber'
|
|
31
|
-
import { VFXParticles
|
|
32
|
-
import * as THREE from 'three/webgpu'
|
|
30
|
+
import { VFXParticles } from 'r3f-vfx'
|
|
33
31
|
|
|
34
32
|
function App() {
|
|
35
33
|
return (
|
|
@@ -40,7 +38,64 @@ function App() {
|
|
|
40
38
|
}
|
|
41
39
|
```
|
|
42
40
|
|
|
43
|
-
|
|
41
|
+
### Vanilla Three.js (Experimental)
|
|
42
|
+
|
|
43
|
+
Add it to your vanilla Three.js project with:
|
|
44
|
+
|
|
45
|
+
```bash
|
|
46
|
+
npm install vanilla-vfx
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
```ts
|
|
50
|
+
import { VFXParticles } from 'vanilla-vfx'
|
|
51
|
+
|
|
52
|
+
const particles = new VFXParticles(renderer, { debug: true })
|
|
53
|
+
scene.add(particles.renderObject)
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
### TresJS / Vue (Experimental)
|
|
57
|
+
|
|
58
|
+
Add it to your TresJS project with:
|
|
59
|
+
|
|
60
|
+
```bash
|
|
61
|
+
npm install tres-vfx
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
```vue
|
|
65
|
+
<script setup>
|
|
66
|
+
import { TresCanvas } from '@tresjs/core'
|
|
67
|
+
import { VFXParticles } from 'tres-vfx'
|
|
68
|
+
</script>
|
|
69
|
+
|
|
70
|
+
<template>
|
|
71
|
+
<TresCanvas>
|
|
72
|
+
<VFXParticles debug />
|
|
73
|
+
</TresCanvas>
|
|
74
|
+
</template>
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
### Threlte / Svelte (Experimental)
|
|
78
|
+
|
|
79
|
+
Add it to your Threlte project with:
|
|
80
|
+
|
|
81
|
+
```bash
|
|
82
|
+
npm install threlte-vfx
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
```svelte
|
|
86
|
+
<script>
|
|
87
|
+
import { Canvas } from '@threlte/core'
|
|
88
|
+
import VFXParticles from 'threlte-vfx/VFXParticles.svelte'
|
|
89
|
+
</script>
|
|
90
|
+
|
|
91
|
+
<Canvas>
|
|
92
|
+
<VFXParticles debug />
|
|
93
|
+
</Canvas>
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
## How to use
|
|
97
|
+
|
|
98
|
+
Use the debug panel to design your effect, then copy the generated code and replace it in your code.
|
|
44
99
|
|
|
45
100
|
## API Reference
|
|
46
101
|
|
package/dist/index.d.ts
CHANGED
|
@@ -235,9 +235,7 @@ declare const VFXParticles: vue.DefineComponent<vue.ExtractPropTypes<{
|
|
|
235
235
|
};
|
|
236
236
|
}>, () => vue.VNode<vue.RendererNode, vue.RendererElement, {
|
|
237
237
|
[key: string]: any;
|
|
238
|
-
}> | vue.
|
|
239
|
-
[key: string]: any;
|
|
240
|
-
}>[] | null, {}, {}, {}, vue.ComponentOptionsMixin, vue.ComponentOptionsMixin, {}, string, vue.PublicProps, Readonly<vue.ExtractPropTypes<{
|
|
238
|
+
}> | null, {}, {}, {}, vue.ComponentOptionsMixin, vue.ComponentOptionsMixin, {}, string, vue.PublicProps, Readonly<vue.ExtractPropTypes<{
|
|
241
239
|
name: {
|
|
242
240
|
type: StringConstructor;
|
|
243
241
|
default: undefined;
|
package/dist/index.js
CHANGED
|
@@ -36,14 +36,12 @@ import {
|
|
|
36
36
|
EmitterShape,
|
|
37
37
|
Lighting,
|
|
38
38
|
VFXParticleSystem,
|
|
39
|
-
isWebGPUBackend,
|
|
40
39
|
isNonDefaultRotation,
|
|
41
40
|
normalizeProps,
|
|
42
41
|
updateUniforms,
|
|
43
42
|
updateUniformsPartial,
|
|
44
43
|
resolveFeatures
|
|
45
44
|
} from "core-vfx";
|
|
46
|
-
var warnedWebGL = false;
|
|
47
45
|
var VFXParticles = defineComponent({
|
|
48
46
|
name: "VFXParticles",
|
|
49
47
|
props: {
|
|
@@ -66,13 +64,22 @@ var VFXParticles = defineComponent({
|
|
|
66
64
|
type: null,
|
|
67
65
|
default: () => [1, 0]
|
|
68
66
|
},
|
|
69
|
-
fadeSizeCurve: {
|
|
67
|
+
fadeSizeCurve: {
|
|
68
|
+
type: null,
|
|
69
|
+
default: null
|
|
70
|
+
},
|
|
70
71
|
fadeOpacity: {
|
|
71
72
|
type: null,
|
|
72
73
|
default: () => [1, 0]
|
|
73
74
|
},
|
|
74
|
-
fadeOpacityCurve: {
|
|
75
|
-
|
|
75
|
+
fadeOpacityCurve: {
|
|
76
|
+
type: null,
|
|
77
|
+
default: null
|
|
78
|
+
},
|
|
79
|
+
velocityCurve: {
|
|
80
|
+
type: null,
|
|
81
|
+
default: null
|
|
82
|
+
},
|
|
76
83
|
gravity: {
|
|
77
84
|
type: null,
|
|
78
85
|
default: () => [0, 0, 0]
|
|
@@ -110,7 +117,10 @@ var VFXParticles = defineComponent({
|
|
|
110
117
|
default: Appearance.GRADIENT
|
|
111
118
|
},
|
|
112
119
|
alphaMap: { type: Object, default: null },
|
|
113
|
-
flipbook: {
|
|
120
|
+
flipbook: {
|
|
121
|
+
type: Object,
|
|
122
|
+
default: null
|
|
123
|
+
},
|
|
114
124
|
rotation: {
|
|
115
125
|
type: null,
|
|
116
126
|
default: () => [0, 0]
|
|
@@ -119,7 +129,10 @@ var VFXParticles = defineComponent({
|
|
|
119
129
|
type: null,
|
|
120
130
|
default: () => [0, 0]
|
|
121
131
|
},
|
|
122
|
-
rotationSpeedCurve: {
|
|
132
|
+
rotationSpeedCurve: {
|
|
133
|
+
type: null,
|
|
134
|
+
default: null
|
|
135
|
+
},
|
|
123
136
|
geometry: {
|
|
124
137
|
type: Object,
|
|
125
138
|
default: null
|
|
@@ -149,8 +162,14 @@ var VFXParticles = defineComponent({
|
|
|
149
162
|
backdropNode: { type: null, default: null },
|
|
150
163
|
opacityNode: { type: null, default: null },
|
|
151
164
|
colorNode: { type: null, default: null },
|
|
152
|
-
alphaTestNode: {
|
|
153
|
-
|
|
165
|
+
alphaTestNode: {
|
|
166
|
+
type: null,
|
|
167
|
+
default: null
|
|
168
|
+
},
|
|
169
|
+
castShadowNode: {
|
|
170
|
+
type: null,
|
|
171
|
+
default: null
|
|
172
|
+
},
|
|
154
173
|
emitCount: { type: Number, default: 1 },
|
|
155
174
|
emitterShape: {
|
|
156
175
|
type: null,
|
|
@@ -186,18 +205,20 @@ var VFXParticles = defineComponent({
|
|
|
186
205
|
type: Object,
|
|
187
206
|
default: null
|
|
188
207
|
},
|
|
189
|
-
curveTexturePath: {
|
|
208
|
+
curveTexturePath: {
|
|
209
|
+
type: null,
|
|
210
|
+
default: null
|
|
211
|
+
},
|
|
190
212
|
depthTest: { type: Boolean, default: true },
|
|
191
213
|
renderOrder: { type: Number, default: 0 }
|
|
192
214
|
},
|
|
193
|
-
setup(props, { expose
|
|
215
|
+
setup(props, { expose }) {
|
|
194
216
|
var _a, _b, _c, _d;
|
|
195
217
|
const { renderer: rendererCtx } = useTresContext();
|
|
196
218
|
const { onBeforeRender } = useLoop();
|
|
197
219
|
const systemRef = shallowRef(null);
|
|
198
220
|
const renderObjectRef = shallowRef(null);
|
|
199
221
|
const emitting = ref(props.autoStart);
|
|
200
|
-
const isWebGPU = ref(false);
|
|
201
222
|
const debugValuesRef = ref(null);
|
|
202
223
|
const activeMaxParticles = ref(props.maxParticles);
|
|
203
224
|
const activeLighting = ref(props.lighting);
|
|
@@ -315,17 +336,6 @@ var VFXParticles = defineComponent({
|
|
|
315
336
|
console.warn("tres-vfx: No renderer instance available");
|
|
316
337
|
return;
|
|
317
338
|
}
|
|
318
|
-
if (!isWebGPUBackend(renderer)) {
|
|
319
|
-
if (!warnedWebGL) {
|
|
320
|
-
warnedWebGL = true;
|
|
321
|
-
console.warn(
|
|
322
|
-
"tres-vfx: WebGPU backend not detected. Particle system disabled."
|
|
323
|
-
);
|
|
324
|
-
}
|
|
325
|
-
isWebGPU.value = false;
|
|
326
|
-
return;
|
|
327
|
-
}
|
|
328
|
-
isWebGPU.value = true;
|
|
329
339
|
const system = createSystem();
|
|
330
340
|
if (!system) return;
|
|
331
341
|
systemRef.value = system;
|
|
@@ -412,7 +422,8 @@ var VFXParticles = defineComponent({
|
|
|
412
422
|
if (newValues.position) {
|
|
413
423
|
system.setPosition(newValues.position);
|
|
414
424
|
}
|
|
415
|
-
if ("delay" in newValues)
|
|
425
|
+
if ("delay" in newValues)
|
|
426
|
+
system.setDelay((_g = newValues.delay) != null ? _g : 0);
|
|
416
427
|
if ("emitCount" in newValues)
|
|
417
428
|
system.setEmitCount((_h = newValues.emitCount) != null ? _h : 1);
|
|
418
429
|
if (newValues.autoStart !== void 0) {
|
|
@@ -456,7 +467,10 @@ var VFXParticles = defineComponent({
|
|
|
456
467
|
}
|
|
457
468
|
activeGeometry.value = null;
|
|
458
469
|
} else {
|
|
459
|
-
const newGeometry = createGeometry(
|
|
470
|
+
const newGeometry = createGeometry(
|
|
471
|
+
geoType,
|
|
472
|
+
geoArgs
|
|
473
|
+
);
|
|
460
474
|
if (newGeometry) {
|
|
461
475
|
if (activeGeometry.value !== null && activeGeometry.value !== props.geometry) {
|
|
462
476
|
activeGeometry.value.dispose();
|
|
@@ -583,7 +597,6 @@ var VFXParticles = defineComponent({
|
|
|
583
597
|
activeRotationSpeedCurve
|
|
584
598
|
],
|
|
585
599
|
() => {
|
|
586
|
-
if (!isWebGPU.value) return;
|
|
587
600
|
initSystem();
|
|
588
601
|
}
|
|
589
602
|
);
|
|
@@ -728,9 +741,6 @@ var VFXParticles = defineComponent({
|
|
|
728
741
|
};
|
|
729
742
|
expose(api);
|
|
730
743
|
return () => {
|
|
731
|
-
if (!isWebGPU.value) {
|
|
732
|
-
return slots.fallback ? slots.fallback() : null;
|
|
733
|
-
}
|
|
734
744
|
const obj = renderObjectRef.value;
|
|
735
745
|
if (!obj) return null;
|
|
736
746
|
return h("primitive", { object: obj });
|
|
@@ -744,7 +754,6 @@ import { useLoop as useLoop2, useTresContext as useTresContext2 } from "@tresjs/
|
|
|
744
754
|
import { Vector3, Quaternion } from "three/webgpu";
|
|
745
755
|
import {
|
|
746
756
|
EmitterController,
|
|
747
|
-
isWebGPUBackend as isWebGPUBackend2,
|
|
748
757
|
coreStore as coreStore2
|
|
749
758
|
} from "core-vfx";
|
|
750
759
|
var worldPos = new Vector3();
|
|
@@ -780,7 +789,6 @@ var VFXEmitter = defineComponent2({
|
|
|
780
789
|
const { renderer } = useTresContext2();
|
|
781
790
|
const { onBeforeRender } = useLoop2();
|
|
782
791
|
const groupRef = ref2(null);
|
|
783
|
-
const isWebGPU = ref2(false);
|
|
784
792
|
const controller = new EmitterController({
|
|
785
793
|
emitCount: props.emitCount,
|
|
786
794
|
delay: props.delay,
|
|
@@ -821,29 +829,24 @@ var VFXEmitter = defineComponent2({
|
|
|
821
829
|
});
|
|
822
830
|
}
|
|
823
831
|
);
|
|
824
|
-
function
|
|
825
|
-
const
|
|
826
|
-
if (
|
|
827
|
-
isWebGPU.value = true;
|
|
828
|
-
const system = getParticleSystem();
|
|
829
|
-
if (system) controller.setSystem(system);
|
|
830
|
-
}
|
|
832
|
+
function linkSystem() {
|
|
833
|
+
const system = getParticleSystem();
|
|
834
|
+
if (system) controller.setSystem(system);
|
|
831
835
|
}
|
|
832
836
|
onMounted2(() => {
|
|
833
837
|
var _a;
|
|
834
838
|
const mgr = renderer;
|
|
835
839
|
if ((_a = mgr.isInitialized) == null ? void 0 : _a.value) {
|
|
836
|
-
|
|
840
|
+
linkSystem();
|
|
837
841
|
} else if (mgr.onReady) {
|
|
838
842
|
mgr.onReady(() => {
|
|
839
|
-
|
|
843
|
+
linkSystem();
|
|
840
844
|
});
|
|
841
845
|
} else {
|
|
842
|
-
|
|
846
|
+
linkSystem();
|
|
843
847
|
}
|
|
844
848
|
});
|
|
845
849
|
onBeforeRender(({ delta }) => {
|
|
846
|
-
if (!isWebGPU.value) return;
|
|
847
850
|
if (!controller.getSystem()) {
|
|
848
851
|
const system = getParticleSystem();
|
|
849
852
|
if (system) controller.setSystem(system);
|
|
@@ -915,61 +918,31 @@ var VFXEmitter = defineComponent2({
|
|
|
915
918
|
}
|
|
916
919
|
});
|
|
917
920
|
function useVFXEmitter(name) {
|
|
918
|
-
const { renderer } = useTresContext2();
|
|
919
|
-
const isWebGPU = ref2(false);
|
|
920
|
-
onMounted2(() => {
|
|
921
|
-
var _a;
|
|
922
|
-
const mgr = renderer;
|
|
923
|
-
if ((_a = mgr.isInitialized) == null ? void 0 : _a.value) {
|
|
924
|
-
const r = renderer.instance;
|
|
925
|
-
if (r && isWebGPUBackend2(r)) {
|
|
926
|
-
isWebGPU.value = true;
|
|
927
|
-
}
|
|
928
|
-
} else if (mgr.onReady) {
|
|
929
|
-
mgr.onReady((r) => {
|
|
930
|
-
if (isWebGPUBackend2(r)) {
|
|
931
|
-
isWebGPU.value = true;
|
|
932
|
-
}
|
|
933
|
-
});
|
|
934
|
-
} else {
|
|
935
|
-
const r = renderer.instance;
|
|
936
|
-
if (r && isWebGPUBackend2(r)) {
|
|
937
|
-
isWebGPU.value = true;
|
|
938
|
-
}
|
|
939
|
-
}
|
|
940
|
-
});
|
|
941
921
|
const getParticles = () => coreStore2.getState().getParticles(name);
|
|
942
922
|
const emit = (position = [0, 0, 0], count = 20, overrides = null) => {
|
|
943
|
-
if (!isWebGPU.value) return false;
|
|
944
923
|
const [x, y, z] = position;
|
|
945
924
|
return coreStore2.getState().emit(name, { x, y, z, count, overrides });
|
|
946
925
|
};
|
|
947
926
|
const burst = (position = [0, 0, 0], count = 50, overrides = null) => {
|
|
948
|
-
if (!isWebGPU.value) return false;
|
|
949
927
|
const [x, y, z] = position;
|
|
950
928
|
return coreStore2.getState().emit(name, { x, y, z, count, overrides });
|
|
951
929
|
};
|
|
952
930
|
const start = () => {
|
|
953
|
-
if (!isWebGPU.value) return false;
|
|
954
931
|
return coreStore2.getState().start(name);
|
|
955
932
|
};
|
|
956
933
|
const stop = () => {
|
|
957
|
-
if (!isWebGPU.value) return false;
|
|
958
934
|
return coreStore2.getState().stop(name);
|
|
959
935
|
};
|
|
960
936
|
const clear = () => {
|
|
961
|
-
if (!isWebGPU.value) return false;
|
|
962
937
|
return coreStore2.getState().clear(name);
|
|
963
938
|
};
|
|
964
939
|
const isEmitting = () => {
|
|
965
940
|
var _a;
|
|
966
|
-
if (!isWebGPU.value) return false;
|
|
967
941
|
const particles = getParticles();
|
|
968
942
|
return (_a = particles == null ? void 0 : particles.isEmitting) != null ? _a : false;
|
|
969
943
|
};
|
|
970
944
|
const getUniforms = () => {
|
|
971
945
|
var _a;
|
|
972
|
-
if (!isWebGPU.value) return null;
|
|
973
946
|
const particles = getParticles();
|
|
974
947
|
return (_a = particles == null ? void 0 : particles.uniforms) != null ? _a : null;
|
|
975
948
|
};
|
|
@@ -981,12 +954,12 @@ function useVFXEmitter(name) {
|
|
|
981
954
|
clear,
|
|
982
955
|
isEmitting,
|
|
983
956
|
getUniforms,
|
|
984
|
-
getParticles
|
|
957
|
+
getParticles
|
|
985
958
|
};
|
|
986
959
|
}
|
|
987
960
|
|
|
988
961
|
// src/vue-store.ts
|
|
989
|
-
import { ref as ref3, onUnmounted as
|
|
962
|
+
import { ref as ref3, onUnmounted as onUnmounted2 } from "vue";
|
|
990
963
|
import { coreStore as coreStore3 } from "core-vfx";
|
|
991
964
|
function useVFXStore(selector) {
|
|
992
965
|
const pick = selector != null ? selector : ((s) => s);
|
|
@@ -994,7 +967,7 @@ function useVFXStore(selector) {
|
|
|
994
967
|
const unsubscribe = coreStore3.subscribe((s) => {
|
|
995
968
|
state.value = pick(s);
|
|
996
969
|
});
|
|
997
|
-
|
|
970
|
+
onUnmounted2(unsubscribe);
|
|
998
971
|
return state;
|
|
999
972
|
}
|
|
1000
973
|
useVFXStore.getState = coreStore3.getState;
|
|
@@ -1017,7 +990,7 @@ import {
|
|
|
1017
990
|
import {
|
|
1018
991
|
VFXParticleSystem as VFXParticleSystem2,
|
|
1019
992
|
EmitterController as EmitterController2,
|
|
1020
|
-
isWebGPUBackend
|
|
993
|
+
isWebGPUBackend,
|
|
1021
994
|
isNonDefaultRotation as isNonDefaultRotation2,
|
|
1022
995
|
normalizeProps as normalizeProps2,
|
|
1023
996
|
resolveCurveTexture
|
|
@@ -1038,7 +1011,7 @@ export {
|
|
|
1038
1011
|
buildCurveTextureBin,
|
|
1039
1012
|
createCombinedCurveTexture,
|
|
1040
1013
|
isNonDefaultRotation2 as isNonDefaultRotation,
|
|
1041
|
-
|
|
1014
|
+
isWebGPUBackend,
|
|
1042
1015
|
normalizeProps2 as normalizeProps,
|
|
1043
1016
|
resolveCurveTexture,
|
|
1044
1017
|
useVFXEmitter,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "tres-vfx",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.0",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"repository": {
|
|
@@ -24,8 +24,8 @@
|
|
|
24
24
|
"prepublishOnly": "bun run typecheck && bun run build && bun run copy-readme"
|
|
25
25
|
},
|
|
26
26
|
"dependencies": {
|
|
27
|
-
"core-vfx": "0.
|
|
28
|
-
"debug-vfx": "0.1.
|
|
27
|
+
"core-vfx": "0.2.0",
|
|
28
|
+
"debug-vfx": "0.1.2"
|
|
29
29
|
},
|
|
30
30
|
"peerDependencies": {
|
|
31
31
|
"@tresjs/core": ">=5.0.0",
|