r3f-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
@@ -9,8 +9,6 @@ type VFXParticlesProps = VFXParticleSystemOptions & {
9
9
  name?: string;
10
10
  /** Show debug control panel */
11
11
  debug?: boolean;
12
- /** Optional fallback content to render when WebGPU is not available */
13
- fallback?: React.ReactNode;
14
12
  };
15
13
  declare const VFXParticles: react.ForwardRefExoticComponent<core_vfx.BaseParticleProps & {
16
14
  backdropNode?: any | ((data: core_vfx.ParticleData) => any) | null;
@@ -26,8 +24,6 @@ declare const VFXParticles: react.ForwardRefExoticComponent<core_vfx.BaseParticl
26
24
  name?: string;
27
25
  /** Show debug control panel */
28
26
  debug?: boolean;
29
- /** Optional fallback content to render when WebGPU is not available */
30
- fallback?: React.ReactNode;
31
27
  } & react.RefAttributes<unknown>>;
32
28
 
33
29
  interface VFXEmitterProps extends EmitterControllerOptions {
package/dist/index.js CHANGED
@@ -17,18 +17,6 @@ var __spreadValues = (a, b) => {
17
17
  return a;
18
18
  };
19
19
  var __spreadProps = (a, b) => __defProps(a, __getOwnPropDescs(b));
20
- var __objRest = (source, exclude) => {
21
- var target = {};
22
- for (var prop in source)
23
- if (__hasOwnProp.call(source, prop) && exclude.indexOf(prop) < 0)
24
- target[prop] = source[prop];
25
- if (source != null && __getOwnPropSymbols)
26
- for (var prop of __getOwnPropSymbols(source)) {
27
- if (exclude.indexOf(prop) < 0 && __propIsEnum.call(source, prop))
28
- target[prop] = source[prop];
29
- }
30
- return target;
31
- };
32
20
 
33
21
  // src/VFXParticles.tsx
34
22
  import {
@@ -62,7 +50,6 @@ import {
62
50
  EmitterShape,
63
51
  Lighting,
64
52
  VFXParticleSystem,
65
- isWebGPUBackend,
66
53
  isNonDefaultRotation,
67
54
  normalizeProps,
68
55
  updateUniforms,
@@ -81,9 +68,9 @@ import {
81
68
  buildCurveTextureBin,
82
69
  CurveChannel
83
70
  } from "core-vfx";
84
- import { Fragment, jsx } from "react/jsx-runtime";
85
- var VFXParticlesImpl = forwardRef(
86
- function VFXParticlesImpl2({
71
+ import { jsx } from "react/jsx-runtime";
72
+ var VFXParticles = forwardRef(
73
+ function VFXParticles2({
87
74
  name,
88
75
  maxParticles = 1e4,
89
76
  size = [0.1, 0.3],
@@ -658,40 +645,18 @@ var VFXParticlesImpl = forwardRef(
658
645
  return /* @__PURE__ */ jsx("primitive", { ref: spriteRef, object: system.renderObject });
659
646
  }
660
647
  );
661
- var warnedWebGL = false;
662
- var VFXParticles = forwardRef(
663
- function VFXParticles2(_a, ref) {
664
- var _b = _a, { fallback } = _b, props = __objRest(_b, ["fallback"]);
665
- const { gl } = useThree();
666
- const isWebGPU = useMemo(() => isWebGPUBackend(gl), [gl]);
667
- if (!isWebGPU) {
668
- if (!warnedWebGL) {
669
- warnedWebGL = true;
670
- console.warn(
671
- "r3f-vfx: WebGPU backend not detected. Particle system disabled."
672
- );
673
- }
674
- return /* @__PURE__ */ jsx(Fragment, { children: fallback != null ? fallback : null });
675
- }
676
- return /* @__PURE__ */ jsx(VFXParticlesImpl, __spreadValues({ ref }, props));
677
- }
678
- );
679
648
 
680
649
  // src/VFXEmitter.tsx
681
650
  import {
682
651
  useRef as useRef2,
683
652
  useEffect as useEffect2,
684
653
  useCallback as useCallback2,
685
- useMemo as useMemo2,
686
654
  forwardRef as forwardRef2,
687
655
  useImperativeHandle as useImperativeHandle2
688
656
  } from "react";
689
- import { useFrame as useFrame2, useThree as useThree2 } from "@react-three/fiber";
657
+ import { useFrame as useFrame2 } from "@react-three/fiber";
690
658
  import { Vector3, Quaternion } from "three/webgpu";
691
- import {
692
- EmitterController,
693
- isWebGPUBackend as isWebGPUBackend2
694
- } from "core-vfx";
659
+ import { EmitterController } from "core-vfx";
695
660
  import { jsx as jsx2 } from "react/jsx-runtime";
696
661
  var worldPos = new Vector3();
697
662
  var worldQuat = new Quaternion();
@@ -709,8 +674,6 @@ var VFXEmitter = forwardRef2(function VFXEmitter2({
709
674
  onEmit,
710
675
  children
711
676
  }, ref) {
712
- const { gl } = useThree2();
713
- const isWebGPU = useMemo2(() => isWebGPUBackend2(gl), [gl]);
714
677
  const groupRef = useRef2(null);
715
678
  const controllerRef = useRef2(null);
716
679
  if (!controllerRef.current) {
@@ -733,10 +696,9 @@ var VFXEmitter = forwardRef2(function VFXEmitter2({
733
696
  return useVFXStore.getState().getParticles(name);
734
697
  }, [name, particlesRef]);
735
698
  useEffect2(() => {
736
- if (!isWebGPU) return;
737
699
  const system = getParticleSystem();
738
700
  controller.setSystem(system);
739
- }, [isWebGPU, getParticleSystem, controller]);
701
+ }, [getParticleSystem, controller]);
740
702
  useEffect2(() => {
741
703
  controller.updateOptions({
742
704
  emitCount,
@@ -760,7 +722,6 @@ var VFXEmitter = forwardRef2(function VFXEmitter2({
760
722
  onEmit
761
723
  ]);
762
724
  useFrame2((_, delta) => {
763
- if (!isWebGPU) return;
764
725
  if (!controller.getSystem()) {
765
726
  const system = getParticleSystem();
766
727
  if (system) controller.setSystem(system);
@@ -830,8 +791,6 @@ var VFXEmitter = forwardRef2(function VFXEmitter2({
830
791
  );
831
792
  });
832
793
  function useVFXEmitter(name) {
833
- const { gl } = useThree2();
834
- const isWebGPU = useMemo2(() => isWebGPUBackend2(gl), [gl]);
835
794
  const getParticles = useVFXStore((s) => s.getParticles);
836
795
  const storeEmit = useVFXStore((s) => s.emit);
837
796
  const storeStart = useVFXStore((s) => s.start);
@@ -839,44 +798,37 @@ function useVFXEmitter(name) {
839
798
  const storeClear = useVFXStore((s) => s.clear);
840
799
  const emit = useCallback2(
841
800
  (position = [0, 0, 0], count = 20, overrides = null) => {
842
- if (!isWebGPU) return false;
843
801
  const [x, y, z] = position;
844
802
  return storeEmit(name, { x, y, z, count, overrides });
845
803
  },
846
- [isWebGPU, name, storeEmit]
804
+ [name, storeEmit]
847
805
  );
848
806
  const burst = useCallback2(
849
807
  (position = [0, 0, 0], count = 50, overrides = null) => {
850
- if (!isWebGPU) return false;
851
808
  const [x, y, z] = position;
852
809
  return storeEmit(name, { x, y, z, count, overrides });
853
810
  },
854
- [isWebGPU, name, storeEmit]
811
+ [name, storeEmit]
855
812
  );
856
813
  const start = useCallback2(() => {
857
- if (!isWebGPU) return false;
858
814
  return storeStart(name);
859
- }, [isWebGPU, name, storeStart]);
815
+ }, [name, storeStart]);
860
816
  const stop = useCallback2(() => {
861
- if (!isWebGPU) return false;
862
817
  return storeStop(name);
863
- }, [isWebGPU, name, storeStop]);
818
+ }, [name, storeStop]);
864
819
  const clear = useCallback2(() => {
865
- if (!isWebGPU) return false;
866
820
  return storeClear(name);
867
- }, [isWebGPU, name, storeClear]);
821
+ }, [name, storeClear]);
868
822
  const isEmitting = useCallback2(() => {
869
823
  var _a;
870
- if (!isWebGPU) return false;
871
824
  const particles = getParticles(name);
872
825
  return (_a = particles == null ? void 0 : particles.isEmitting) != null ? _a : false;
873
- }, [isWebGPU, name, getParticles]);
826
+ }, [name, getParticles]);
874
827
  const getUniforms = useCallback2(() => {
875
828
  var _a;
876
- if (!isWebGPU) return null;
877
829
  const particles = getParticles(name);
878
830
  return (_a = particles == null ? void 0 : particles.uniforms) != null ? _a : null;
879
- }, [isWebGPU, name, getParticles]);
831
+ }, [name, getParticles]);
880
832
  return {
881
833
  emit,
882
834
  burst,
@@ -885,7 +837,7 @@ function useVFXEmitter(name) {
885
837
  clear,
886
838
  isEmitting,
887
839
  getUniforms,
888
- getParticles: () => isWebGPU ? getParticles(name) : null
840
+ getParticles: () => getParticles(name)
889
841
  };
890
842
  }
891
843
 
@@ -893,7 +845,7 @@ function useVFXEmitter(name) {
893
845
  import {
894
846
  VFXParticleSystem as VFXParticleSystem2,
895
847
  EmitterController as EmitterController2,
896
- isWebGPUBackend as isWebGPUBackend3,
848
+ isWebGPUBackend,
897
849
  isNonDefaultRotation as isNonDefaultRotation2,
898
850
  normalizeProps as normalizeProps2,
899
851
  resolveCurveTexture
@@ -914,7 +866,7 @@ export {
914
866
  buildCurveTextureBin,
915
867
  createCombinedCurveTexture,
916
868
  isNonDefaultRotation2 as isNonDefaultRotation,
917
- isWebGPUBackend3 as isWebGPUBackend,
869
+ isWebGPUBackend,
918
870
  normalizeProps2 as normalizeProps,
919
871
  resolveCurveTexture,
920
872
  useVFXEmitter,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "r3f-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 copy-readme"
25
25
  },
26
26
  "dependencies": {
27
- "core-vfx": "0.1.0",
28
- "debug-vfx": "0.1.0",
27
+ "core-vfx": "0.2.0",
28
+ "debug-vfx": "0.1.2",
29
29
  "zustand": "5.0.10"
30
30
  },
31
31
  "devDependencies": {