threlte-vfx 0.2.1 → 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
@@ -13,9 +13,7 @@ Available for React Three Fiber (R3F), and experimentally for vanilla Three.js,
13
13
  - 📊 **Curve-based Control** - Bezier curves for size, opacity, velocity, and rotation over lifetime
14
14
  - 🔗 **Emitter System** - Decoupled emitters that can share particle systems
15
15
  - ⚡ **WebGPU Native** - Built specifically for Three.js WebGPU renderer
16
-
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.
18
-
16
+ - 🐢 **WebGL fallback** – Three VFX targets WebGPU ([79% global support](https://caniuse.com/webgpu)) but provides a CPU fallback
19
17
 
20
18
  ## Quick Start
21
19
 
@@ -65,8 +63,8 @@ npm install tres-vfx
65
63
 
66
64
  ```vue
67
65
  <script setup>
68
- import { TresCanvas } from '@tresjs/core'
69
- import { VFXParticles } from 'tres-vfx'
66
+ import { TresCanvas } from '@tresjs/core'
67
+ import { VFXParticles } from 'tres-vfx'
70
68
  </script>
71
69
 
72
70
  <template>
@@ -4,7 +4,6 @@ import { onMount } from 'svelte'
4
4
  import { Vector3, Quaternion, Group } from 'three/webgpu'
5
5
  import {
6
6
  EmitterController,
7
- isWebGPUBackend,
8
7
  coreStore,
9
8
  type EmitterControllerOptions,
10
9
  } from 'core-vfx'
@@ -44,7 +43,6 @@ let {
44
43
  const { renderer } = useThrelte()
45
44
 
46
45
  let groupRef: Group | null = $state(null)
47
- let isWebGPU = $state(false)
48
46
 
49
47
  const controller = new EmitterController({
50
48
  emitCount,
@@ -89,22 +87,13 @@ $effect(() => {
89
87
  })
90
88
  })
91
89
 
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
90
  onMount(() => {
101
- checkWebGPU()
91
+ const system = getParticleSystem()
92
+ if (system) controller.setSystem(system)
102
93
  })
103
94
 
104
95
  // Frame loop
105
96
  useTask((delta) => {
106
- if (!isWebGPU) return
107
-
108
97
  if (!controller.getSystem()) {
109
98
  const system = getParticleSystem()
110
99
  if (system) controller.setSystem(system)
@@ -9,7 +9,6 @@ import {
9
9
  EmitterShape,
10
10
  Lighting,
11
11
  VFXParticleSystem,
12
- isWebGPUBackend,
13
12
  isNonDefaultRotation,
14
13
  normalizeProps,
15
14
  updateUniforms,
@@ -144,18 +143,15 @@ let {
144
143
 
145
144
  const { renderer } = useThrelte()
146
145
 
147
- let warnedWebGL = false
148
146
  let mounted = false
149
147
 
150
148
  // Internal state — NOT tracked by effects (plain variables)
151
149
  let _system: VFXParticleSystem | null = null
152
150
  let _renderObject: THREE.Object3D | null = null
153
- let _isWebGPU = false
154
151
  let _emitting = autoStart
155
152
 
156
153
  // Reactive state for the template only
157
154
  let renderObjectForTemplate: THREE.Object3D | null = $state(null)
158
- let isWebGPUForTemplate = $state(false)
159
155
 
160
156
  let debugValues: Record<string, unknown> | null = null
161
157
 
@@ -266,7 +262,6 @@ function destroySystem() {
266
262
  _system = null
267
263
  _renderObject = null
268
264
  renderObjectForTemplate = null
269
- isWebGPUForTemplate = false
270
265
  }
271
266
 
272
267
  function initSystem() {
@@ -285,20 +280,6 @@ function initSystem() {
285
280
  return
286
281
  }
287
282
 
288
- if (!isWebGPUBackend(renderer)) {
289
- if (!warnedWebGL) {
290
- warnedWebGL = true
291
- console.warn(
292
- 'threlte-vfx: WebGPU backend not detected. Particle system disabled.'
293
- )
294
- }
295
- _isWebGPU = false
296
- isWebGPUForTemplate = false
297
- renderObjectForTemplate = null
298
- return
299
- }
300
-
301
- _isWebGPU = true
302
283
  const newSystem = createSystem()
303
284
  if (!newSystem) return
304
285
 
@@ -334,7 +315,6 @@ function initSystem() {
334
315
  }
335
316
 
336
317
  // Update template-facing reactive state last
337
- isWebGPUForTemplate = true
338
318
  renderObjectForTemplate = newSystem.renderObject
339
319
  }
340
320
 
@@ -627,7 +607,6 @@ $effect(() => {
627
607
  // Use untrack for the actual init to avoid reading/writing
628
608
  // reactive state that would re-trigger this effect
629
609
  untrack(() => {
630
- if (!_isWebGPU) return
631
610
  initSystem()
632
611
  })
633
612
  })
@@ -768,6 +747,6 @@ export function clear() {
768
747
  }
769
748
  </script>
770
749
 
771
- {#if isWebGPUForTemplate && renderObjectForTemplate}
750
+ {#if renderObjectForTemplate}
772
751
  <T is={renderObjectForTemplate} />
773
752
  {/if}
package/dist/index.d.ts CHANGED
@@ -65,7 +65,13 @@ interface VFXParticlesProps {
65
65
  }
66
66
 
67
67
  declare class VFXParticles extends SvelteComponent<VFXParticlesProps> {
68
- spawn(x?: number, y?: number, z?: number, count?: number, overrides?: Record<string, unknown> | null): void
68
+ spawn(
69
+ x?: number,
70
+ y?: number,
71
+ z?: number,
72
+ count?: number,
73
+ overrides?: Record<string, unknown> | null
74
+ ): void
69
75
  start(): void
70
76
  stop(): void
71
77
  clear(): void
package/dist/index.js CHANGED
@@ -31,7 +31,6 @@ import {
31
31
  EmitterShape,
32
32
  Lighting,
33
33
  VFXParticleSystem,
34
- isWebGPUBackend,
35
34
  isNonDefaultRotation,
36
35
  normalizeProps,
37
36
  updateUniforms,
@@ -43,14 +42,11 @@ function VFXParticles($$anchor, $$props) {
43
42
  $.push($$props, true);
44
43
  let name = $.prop($$props, "name", 19, () => void 0), debug = $.prop($$props, "debug", 3, false), maxParticles = $.prop($$props, "maxParticles", 3, 1e4), size = $.prop($$props, "size", 19, () => [0.1, 0.3]), colorStart = $.prop($$props, "colorStart", 19, () => ["#ffffff"]), colorEnd = $.prop($$props, "colorEnd", 3, null), fadeSize = $.prop($$props, "fadeSize", 19, () => [1, 0]), fadeSizeCurve = $.prop($$props, "fadeSizeCurve", 3, null), fadeOpacity = $.prop($$props, "fadeOpacity", 19, () => [1, 0]), fadeOpacityCurve = $.prop($$props, "fadeOpacityCurve", 3, null), velocityCurve = $.prop($$props, "velocityCurve", 3, null), gravity = $.prop($$props, "gravity", 19, () => [0, 0, 0]), lifetime = $.prop($$props, "lifetime", 19, () => [1, 2]), direction = $.prop($$props, "direction", 19, () => [[-1, 1], [0, 1], [-1, 1]]), startPosition = $.prop($$props, "startPosition", 19, () => [[0, 0], [0, 0], [0, 0]]), speed = $.prop($$props, "speed", 19, () => [0.1, 0.1]), friction = $.prop($$props, "friction", 19, () => ({ intensity: 0, easing: "linear" })), appearance = $.prop($$props, "appearance", 19, () => Appearance.GRADIENT), alphaMap = $.prop($$props, "alphaMap", 3, null), flipbook = $.prop($$props, "flipbook", 3, null), rotation = $.prop($$props, "rotation", 19, () => [0, 0]), rotationSpeed = $.prop($$props, "rotationSpeed", 19, () => [0, 0]), rotationSpeedCurve = $.prop($$props, "rotationSpeedCurve", 3, null), geometry = $.prop($$props, "geometry", 3, null), orientToDirection = $.prop($$props, "orientToDirection", 3, false), orientAxis = $.prop($$props, "orientAxis", 3, "z"), stretchBySpeed = $.prop($$props, "stretchBySpeed", 3, null), lighting = $.prop($$props, "lighting", 19, () => Lighting.STANDARD), shadow = $.prop($$props, "shadow", 3, false), blending = $.prop($$props, "blending", 19, () => Blending.NORMAL), intensity = $.prop($$props, "intensity", 3, 1), position = $.prop($$props, "position", 19, () => [0, 0, 0]), autoStart = $.prop($$props, "autoStart", 3, true), delay = $.prop($$props, "delay", 3, 0), backdropNode = $.prop($$props, "backdropNode", 3, null), opacityNode = $.prop($$props, "opacityNode", 3, null), colorNode = $.prop($$props, "colorNode", 3, null), alphaTestNode = $.prop($$props, "alphaTestNode", 3, null), castShadowNode = $.prop($$props, "castShadowNode", 3, null), emitCount = $.prop($$props, "emitCount", 3, 1), emitterShape = $.prop($$props, "emitterShape", 19, () => EmitterShape.BOX), emitterRadius = $.prop($$props, "emitterRadius", 19, () => [0, 1]), emitterAngle = $.prop($$props, "emitterAngle", 19, () => Math.PI / 4), emitterHeight = $.prop($$props, "emitterHeight", 19, () => [0, 1]), emitterSurfaceOnly = $.prop($$props, "emitterSurfaceOnly", 3, false), emitterDirection = $.prop($$props, "emitterDirection", 19, () => [0, 1, 0]), turbulence = $.prop($$props, "turbulence", 3, null), attractors = $.prop($$props, "attractors", 3, null), attractToCenter = $.prop($$props, "attractToCenter", 3, false), startPositionAsDirection = $.prop($$props, "startPositionAsDirection", 3, false), softParticles = $.prop($$props, "softParticles", 3, false), softDistance = $.prop($$props, "softDistance", 3, 0.5), collision = $.prop($$props, "collision", 3, null), curveTexturePath = $.prop($$props, "curveTexturePath", 3, null), depthTest = $.prop($$props, "depthTest", 3, true), renderOrder = $.prop($$props, "renderOrder", 3, 0);
45
44
  const { renderer } = useThrelte();
46
- let warnedWebGL = false;
47
45
  let mounted = false;
48
46
  let _system = null;
49
47
  let _renderObject = null;
50
- let _isWebGPU = false;
51
48
  let _emitting = autoStart();
52
49
  let renderObjectForTemplate = $.state(null);
53
- let isWebGPUForTemplate = $.state(false);
54
50
  let debugValues = null;
55
51
  let activeMaxParticles = $.state($.proxy(maxParticles()));
56
52
  let activeLighting = $.state($.proxy(lighting()));
@@ -143,7 +139,6 @@ function VFXParticles($$anchor, $$props) {
143
139
  _system = null;
144
140
  _renderObject = null;
145
141
  $.set(renderObjectForTemplate, null);
146
- $.set(isWebGPUForTemplate, false);
147
142
  }
148
143
  function initSystem() {
149
144
  const oldSystem = _system;
@@ -159,17 +154,6 @@ function VFXParticles($$anchor, $$props) {
159
154
  console.warn("threlte-vfx: No renderer instance available");
160
155
  return;
161
156
  }
162
- if (!isWebGPUBackend(renderer)) {
163
- if (!warnedWebGL) {
164
- warnedWebGL = true;
165
- console.warn("threlte-vfx: WebGPU backend not detected. Particle system disabled.");
166
- }
167
- _isWebGPU = false;
168
- $.set(isWebGPUForTemplate, false);
169
- $.set(renderObjectForTemplate, null);
170
- return;
171
- }
172
- _isWebGPU = true;
173
157
  const newSystem = createSystem();
174
158
  if (!newSystem) return;
175
159
  _system = newSystem;
@@ -199,7 +183,6 @@ function VFXParticles($$anchor, $$props) {
199
183
  if (debug()) {
200
184
  initDebugPanel();
201
185
  }
202
- $.set(isWebGPUForTemplate, true);
203
186
  $.set(renderObjectForTemplate, newSystem.renderObject, true);
204
187
  }
205
188
  function handleDebugUpdate(newValues) {
@@ -423,7 +406,6 @@ function VFXParticles($$anchor, $$props) {
423
406
  ];
424
407
  if (!mounted) return;
425
408
  untrack(() => {
426
- if (!_isWebGPU) return;
427
409
  initSystem();
428
410
  });
429
411
  });
@@ -553,7 +535,7 @@ function VFXParticles($$anchor, $$props) {
553
535
  });
554
536
  };
555
537
  $.if(node, ($$render) => {
556
- if ($.get(isWebGPUForTemplate) && $.get(renderObjectForTemplate)) $$render(consequent);
538
+ if ($.get(renderObjectForTemplate)) $$render(consequent);
557
539
  });
558
540
  }
559
541
  $.append($$anchor, fragment);
@@ -566,11 +548,7 @@ import * as $2 from "svelte/internal/client";
566
548
  import { T as T2, useThrelte as useThrelte2, useTask as useTask2 } from "@threlte/core";
567
549
  import { onMount as onMount2 } from "svelte";
568
550
  import { Vector3, Quaternion, Group } from "three/webgpu";
569
- import {
570
- EmitterController,
571
- isWebGPUBackend as isWebGPUBackend2,
572
- coreStore as coreStore2
573
- } from "core-vfx";
551
+ import { EmitterController, coreStore as coreStore2 } from "core-vfx";
574
552
  function VFXEmitter($$anchor, $$props) {
575
553
  $2.push($$props, true);
576
554
  const worldPos = new Vector3();
@@ -578,7 +556,6 @@ function VFXEmitter($$anchor, $$props) {
578
556
  let name = $2.prop($$props, "name", 19, () => void 0), particlesRef = $2.prop($$props, "particlesRef", 19, () => void 0), position = $2.prop($$props, "position", 19, () => [0, 0, 0]), emitCount = $2.prop($$props, "emitCount", 3, 10), delay = $2.prop($$props, "delay", 3, 0), autoStart = $2.prop($$props, "autoStart", 3, true), loop = $2.prop($$props, "loop", 3, true), localDirection = $2.prop($$props, "localDirection", 3, false), direction = $2.prop($$props, "direction", 19, () => void 0), overrides = $2.prop($$props, "overrides", 3, null), onEmit = $2.prop($$props, "onEmit", 19, () => void 0);
579
557
  const { renderer } = useThrelte2();
580
558
  let groupRef = $2.state(null);
581
- let isWebGPU = $2.state(false);
582
559
  const controller = new EmitterController({
583
560
  emitCount: emitCount(),
584
561
  delay: delay(),
@@ -617,18 +594,11 @@ function VFXEmitter($$anchor, $$props) {
617
594
  onEmit: onEmit()
618
595
  });
619
596
  });
620
- function checkWebGPU() {
621
- if (renderer && isWebGPUBackend2(renderer)) {
622
- $2.set(isWebGPU, true);
623
- const system = getParticleSystem();
624
- if (system) controller.setSystem(system);
625
- }
626
- }
627
597
  onMount2(() => {
628
- checkWebGPU();
598
+ const system = getParticleSystem();
599
+ if (system) controller.setSystem(system);
629
600
  });
630
601
  useTask2((delta) => {
631
- if (!$2.get(isWebGPU)) return;
632
602
  if (!controller.getSystem()) {
633
603
  const system = getParticleSystem();
634
604
  if (system) controller.setSystem(system);
@@ -709,11 +679,11 @@ function VFXEmitter($$anchor, $$props) {
709
679
 
710
680
  // src/useVFXEmitter.ts
711
681
  import { useThrelte as useThrelte3 } from "@threlte/core";
712
- import { coreStore as coreStore3, isWebGPUBackend as isWebGPUBackend3 } from "core-vfx";
682
+ import { coreStore as coreStore3, isWebGPUBackend } from "core-vfx";
713
683
  function useVFXEmitter(name) {
714
684
  const { renderer } = useThrelte3();
715
685
  const checkWebGPU = () => {
716
- return renderer && isWebGPUBackend3(renderer);
686
+ return renderer && isWebGPUBackend(renderer);
717
687
  };
718
688
  const getParticles = () => coreStore3.getState().getParticles(name);
719
689
  const emit = (position = [0, 0, 0], count = 20, overrides = null) => {
@@ -796,7 +766,7 @@ import {
796
766
  import {
797
767
  VFXParticleSystem as VFXParticleSystem2,
798
768
  EmitterController as EmitterController2,
799
- isWebGPUBackend as isWebGPUBackend4,
769
+ isWebGPUBackend as isWebGPUBackend2,
800
770
  isNonDefaultRotation as isNonDefaultRotation2,
801
771
  normalizeProps as normalizeProps2,
802
772
  resolveCurveTexture
@@ -817,7 +787,7 @@ export {
817
787
  buildCurveTextureBin,
818
788
  createCombinedCurveTexture,
819
789
  isNonDefaultRotation2 as isNonDefaultRotation,
820
- isWebGPUBackend4 as isWebGPUBackend,
790
+ isWebGPUBackend2 as isWebGPUBackend,
821
791
  normalizeProps2 as normalizeProps,
822
792
  resolveCurveTexture,
823
793
  useVFXEmitter,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "threlte-vfx",
3
- "version": "0.2.1",
3
+ "version": "0.3.0",
4
4
  "license": "MIT",
5
5
  "type": "module",
6
6
  "svelte": "./dist/index.js",
@@ -34,8 +34,8 @@
34
34
  "prepublishOnly": "bun run typecheck && bun run build && bun run copy-readme"
35
35
  },
36
36
  "dependencies": {
37
- "core-vfx": "0.1.0",
38
- "debug-vfx": "0.1.1"
37
+ "core-vfx": "0.2.0",
38
+ "debug-vfx": "0.1.2"
39
39
  },
40
40
  "peerDependencies": {
41
41
  "@threlte/core": ">=8.0.0",