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 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
 
@@ -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
- ## Installation
18
+ ## Quick Start
16
19
 
17
- ```bash
18
- npm install r3f-vfx
19
- ```
20
+ ### React Three Fiber
20
21
 
21
- ### Peer Dependencies
22
+ Add it to your React Three Fiber project with:
22
23
 
23
24
  ```bash
24
- npm install three @react-three/fiber react
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, Appearance, EmitterShape } from 'r3f-vfx'
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
- That's it, start designing in the debug panel, then copy JSX
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.VNode<vue.RendererNode, vue.RendererElement, {
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: { type: null, default: null },
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: { type: null, default: null },
75
- velocityCurve: { type: null, default: null },
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: { type: Object, default: null },
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: { type: null, default: null },
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: { type: null, default: null },
153
- castShadowNode: { type: null, default: null },
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: { type: null, default: null },
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, slots }) {
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) system.setDelay((_g = newValues.delay) != null ? _g : 0);
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(geoType, geoArgs);
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 checkWebGPU() {
825
- const r = renderer.instance;
826
- if (r && isWebGPUBackend2(r)) {
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
- checkWebGPU();
840
+ linkSystem();
837
841
  } else if (mgr.onReady) {
838
842
  mgr.onReady(() => {
839
- checkWebGPU();
843
+ linkSystem();
840
844
  });
841
845
  } else {
842
- checkWebGPU();
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: () => isWebGPU.value ? getParticles() : null
957
+ getParticles
985
958
  };
986
959
  }
987
960
 
988
961
  // src/vue-store.ts
989
- import { ref as ref3, onUnmounted as onUnmounted3 } from "vue";
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
- onUnmounted3(unsubscribe);
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 as isWebGPUBackend3,
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
- isWebGPUBackend3 as isWebGPUBackend,
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.2.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.1.0",
28
- "debug-vfx": "0.1.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",