@newkrok/three-particles 2.15.1 → 2.16.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/llms-full.txt CHANGED
@@ -68,6 +68,7 @@ function animate() {
68
68
  | `dispose()` | `() => void` | Destroy system, free resources |
69
69
  | `update(cycleData)` | `(CycleData) => void` | Update this system individually |
70
70
  | `updateConfig(config)` | `(Partial<ParticleSystemConfig>) => void` | Update configuration at runtime without recreating the system. System-level properties (gravity, force fields, noise, emission, color/size/opacity over lifetime) take effect immediately. Per-particle spawn properties (startColor, startSize, etc.) affect only newly emitted particles. |
71
+ | `computeNode` | `unknown \| null` | GPU compute node for WebGPU dispatch. Non-null when GPU compute is active. Must be dispatched via `renderer.compute(system.computeNode)` every frame before `renderer.render()`. Null when CPU simulation is used. |
71
72
 
72
73
  ---
73
74
 
@@ -96,6 +97,7 @@ type CycleData = {
96
97
  | `maxParticles` | `number` | 100 | Maximum concurrent particles |
97
98
  | `gravity` | `number` | 0.0 | Downward acceleration |
98
99
  | `simulationSpace` | `SimulationSpace` | LOCAL | LOCAL or WORLD coordinate space |
100
+ | `simulationBackend` | `SimulationBackend` | AUTO | Simulation backend: AUTO (GPU if WebGPU available, else CPU), CPU, or GPU |
99
101
 
100
102
  ### Start Properties
101
103
 
@@ -523,6 +525,12 @@ const system = createParticleSystem({
523
525
  ### RendererType
524
526
  `POINTS` (default — classic point sprites) | `INSTANCED` (GPU instanced quads, no gl_PointSize limit) | `TRAIL` (ribbon trails behind particles) | `MESH` (3D mesh particles via GPU instancing)
525
527
 
528
+ ### CollisionPlaneMode
529
+ `KILL` (deactivate particle immediately) | `CLAMP` (stop particle at the plane surface) | `BOUNCE` (reflect velocity with optional dampen)
530
+
531
+ ### SimulationBackend
532
+ `AUTO` (default — GPU compute if WebGPU renderer detected, else CPU) | `CPU` (always JavaScript update loop) | `GPU` (request GPU compute, falls back to CPU if unavailable)
533
+
526
534
  ---
527
535
 
528
536
  ## Callbacks
@@ -693,6 +701,48 @@ createParticleSystem({
693
701
 
694
702
  ---
695
703
 
704
+ ## Collision Planes
705
+
706
+ Infinite planes that constrain particle positions. When a particle crosses from the front side (positive normal direction) to the back side, the configured response mode is triggered.
707
+
708
+ ```typescript
709
+ type CollisionPlaneConfig = {
710
+ isActive?: boolean; // Whether this plane is active (default: true)
711
+ position?: Point3D; // A point on the plane surface (default: {x:0, y:0, z:0})
712
+ normal?: Point3D; // Plane normal vector, defines the "front" side (default: {x:0, y:1, z:0})
713
+ mode?: CollisionPlaneMode; // Response mode (default: KILL)
714
+ dampen?: number; // Velocity dampen factor for BOUNCE mode, 0-1 (default: 0)
715
+ lifetimeLoss?: number; // Fraction of start lifetime to subtract on collision, 0-1 (default: 0)
716
+ };
717
+ ```
718
+
719
+ ### CollisionPlaneMode
720
+ - `KILL` — Deactivate the particle immediately when it crosses the plane
721
+ - `CLAMP` — Stop the particle at the plane surface (zero velocity toward plane)
722
+ - `BOUNCE` — Reflect the velocity vector off the plane normal with optional dampen
723
+
724
+ **Example — Bouncy floor and kill ceiling:**
725
+ ```typescript
726
+ createParticleSystem({
727
+ // ... particle config
728
+ collisionPlanes: [
729
+ {
730
+ position: { x: 0, y: 0, z: 0 },
731
+ normal: { x: 0, y: 1, z: 0 },
732
+ mode: CollisionPlaneMode.BOUNCE,
733
+ dampen: 0.6,
734
+ },
735
+ {
736
+ position: { x: 0, y: 10, z: 0 },
737
+ normal: { x: 0, y: -1, z: 0 },
738
+ mode: CollisionPlaneMode.KILL,
739
+ },
740
+ ],
741
+ });
742
+ ```
743
+
744
+ ---
745
+
696
746
  ## Serialization
697
747
 
698
748
  Save and load particle system configurations as JSON.
@@ -785,6 +835,145 @@ function FireEffect({ config }: { config?: Record<string, unknown> }) {
785
835
 
786
836
  ---
787
837
 
838
+ ## WebGPU Compute Support
839
+
840
+ Optional GPU-accelerated particle simulation via Three.js WebGPU renderer and TSL (Three Shading Language). Offloads all per-particle physics and modifiers to GPU compute shaders, enabling **50K-350K+ particles** at interactive frame rates.
841
+
842
+ **Requirements:** Three.js r182+ with WebGPU build (`three/webgpu`), browser with WebGPU support (Chrome 113+, Edge 113+, Firefox Nightly). No breaking changes — all existing WebGL code works unchanged.
843
+
844
+ ### Setup
845
+
846
+ ```typescript
847
+ // 1. Enable WebGPU support (once, before creating any particle system)
848
+ import { enableWebGPU } from "@newkrok/three-particles/webgpu";
849
+ enableWebGPU();
850
+
851
+ // 2. Create a WebGPU renderer
852
+ import * as THREE from "three/webgpu";
853
+ const renderer = new THREE.WebGPURenderer({ antialias: true });
854
+ await renderer.init();
855
+
856
+ // 3. Create a GPU-accelerated particle system
857
+ import { createParticleSystem, SimulationBackend } from "@newkrok/three-particles";
858
+ const system = createParticleSystem({
859
+ simulationBackend: SimulationBackend.AUTO, // GPU if WebGPU available, else CPU
860
+ maxParticles: 100000,
861
+ // ... rest of config (same API as CPU)
862
+ });
863
+
864
+ scene.add(system.instance);
865
+
866
+ // 4. In your render loop — dispatch compute before rendering
867
+ function animate() {
868
+ system.update({ now: performance.now(), delta, elapsed });
869
+
870
+ if (system.computeNode) {
871
+ renderer.compute(system.computeNode);
872
+ }
873
+ renderer.render(scene, camera);
874
+ }
875
+ ```
876
+
877
+ For fine-grained control (e.g., selective registration of individual WebGPU functions), you can also use `registerTSLMaterialFactory()` directly — see the API reference below.
878
+
879
+ ### SimulationBackend
880
+
881
+ ```typescript
882
+ enum SimulationBackend {
883
+ AUTO = 'AUTO', // GPU compute if WebGPU renderer detected, else CPU (default)
884
+ CPU = 'CPU', // Always JavaScript update loop (works with any renderer)
885
+ GPU = 'GPU', // Request GPU compute; falls back to CPU if renderer lacks compute support
886
+ }
887
+ ```
888
+
889
+ | Value | WebGPU Renderer | WebGL Renderer |
890
+ |-------|----------------|----------------|
891
+ | `AUTO` | GPU compute | CPU (JavaScript) |
892
+ | `CPU` | CPU (JavaScript) | CPU (JavaScript) |
893
+ | `GPU` | GPU compute | CPU (fallback) |
894
+
895
+ ### What Runs on GPU
896
+
897
+ - **Core physics:** gravity, velocity integration, position update, lifetime tracking, death detection
898
+ - **All 7 modifiers:** size over lifetime, opacity over lifetime, color over lifetime (per-channel RGB curves), rotation over lifetime, linear velocity over lifetime (per-axis X/Y/Z curves), orbital velocity, noise (3D simplex FBM)
899
+ - **Force fields:** point attractors/repulsors and directional forces with all falloff modes (NONE, LINEAR, QUADRATIC), up to 16 per system
900
+ - **Collision planes:** kill/clamp/bounce modes with dampen and lifetime loss, encoded as packed uniform buffer
901
+ - **Curves:** baked into 256-sample Float32Array lookup tables at system creation for fast GPU evaluation (<0.4% interpolation error)
902
+
903
+ ### What Stays on CPU
904
+
905
+ - **Emission:** particle activation, burst scheduling, rate-over-time, rate-over-distance
906
+ - **Sub-emitters:** birth/death trigger spawning (sub-emitters are always forced to `SimulationBackend.CPU`)
907
+ - **Configuration changes:** `updateConfig()` applies on the next frame
908
+ - **Trail renderer:** `RendererType.TRAIL` always uses CPU simulation (POINTS, INSTANCED, and MESH work with GPU compute)
909
+
910
+ ### ParticleSystem.computeNode
911
+
912
+ When GPU compute is active, the returned `ParticleSystem` object exposes a `computeNode` property. This must be dispatched every frame **before** `renderer.render()`:
913
+
914
+ ```typescript
915
+ const system = createParticleSystem({ simulationBackend: SimulationBackend.GPU, ... });
916
+
917
+ // In render loop:
918
+ if (system.computeNode) {
919
+ renderer.compute(system.computeNode); // Dispatch GPU compute
920
+ }
921
+ renderer.render(scene, camera);
922
+ ```
923
+
924
+ When CPU simulation is active (no WebGPU, or `simulationBackend: 'CPU'`), `computeNode` is `null`.
925
+
926
+ ### Fallback Behavior
927
+
928
+ WebGPU is fully opt-in and non-breaking:
929
+ - If `enableWebGPU()` not called (or no TSL factory registered via `registerTSLMaterialFactory()`), the library uses GLSL shaders (existing WebGL path)
930
+ - If `simulationBackend: 'GPU'` but the renderer lacks compute support, it silently falls back to CPU
931
+ - The same `ParticleSystemConfig` works identically on both backends — no config changes needed
932
+ - Renderer detection is duck-typed (checks for `.compute()` and `.hasFeature()` methods), not class-based
933
+
934
+ ### WebGPU with React Three Fiber
935
+
936
+ ```tsx
937
+ import { useRef, useEffect } from "react";
938
+ import { useFrame, useThree } from "@react-three/fiber";
939
+ import { createParticleSystem, SimulationBackend } from "@newkrok/three-particles";
940
+ import { enableWebGPU } from "@newkrok/three-particles/webgpu";
941
+
942
+ // Enable once at module level
943
+ enableWebGPU();
944
+
945
+ function GPUParticleEffect({ config }) {
946
+ const groupRef = useRef(null);
947
+ const systemRef = useRef(null);
948
+ const { gl: renderer } = useThree();
949
+
950
+ useEffect(() => {
951
+ const system = createParticleSystem({
952
+ simulationBackend: SimulationBackend.AUTO,
953
+ ...config,
954
+ });
955
+ systemRef.current = system;
956
+ groupRef.current?.add(system.instance);
957
+ return () => system.dispose();
958
+ }, [config]);
959
+
960
+ useFrame((_, delta) => {
961
+ const system = systemRef.current;
962
+ if (!system) return;
963
+ system.update({ now: performance.now(), delta, elapsed: 0 });
964
+ if (system.computeNode) {
965
+ renderer.compute(system.computeNode);
966
+ }
967
+ });
968
+
969
+ return <group ref={groupRef} />;
970
+ }
971
+ ```
972
+
973
+ **Important:** R3F must be configured to use `WebGPURenderer` (via the `gl` prop on `<Canvas>`) for GPU compute to activate.
974
+
975
+ ---
976
+
788
977
  ## Links
789
978
 
790
979
  - Repository: https://github.com/NewKrok/three-particles
package/llms.txt CHANGED
@@ -67,6 +67,7 @@ function animate() {
67
67
  | startColor | MinMaxColor | white | Color range |
68
68
  | gravity | number | 0.0 | Gravity strength |
69
69
  | simulationSpace | LOCAL / WORLD | LOCAL | Coordinate space |
70
+ | simulationBackend | AUTO / CPU / GPU | AUTO | Simulation backend (AUTO: GPU if WebGPU, else CPU) |
70
71
  | emission | Emission | rateOverTime: 10 | Emission config |
71
72
  | shape | ShapeConfig | — | Emitter shape |
72
73
  | map | THREE.Texture | undefined | Particle texture |
@@ -198,6 +199,94 @@ forceFields: [
198
199
  ]
199
200
  ```
200
201
 
202
+ ## Collision Planes
203
+
204
+ Infinite planes that constrain particles with three response modes: kill, clamp, or bounce.
205
+
206
+ ```typescript
207
+ collisionPlanes: [
208
+ { position: { x: 0, y: 0, z: 0 }, normal: { x: 0, y: 1, z: 0 }, mode: CollisionPlaneMode.BOUNCE, dampen: 0.6 },
209
+ { position: { x: 0, y: 5, z: 0 }, normal: { x: 0, y: -1, z: 0 }, mode: CollisionPlaneMode.KILL },
210
+ ]
211
+ ```
212
+
213
+ ## WebGPU Compute Support
214
+
215
+ Optional GPU-accelerated particle simulation via Three.js WebGPU renderer and TSL (Three Shading Language). Enables **50K-350K+ particles** with full physics on the GPU.
216
+
217
+ **Requirements:** Three.js r182+ with WebGPU build (`three/webgpu`), browser with WebGPU support (Chrome 113+, Edge 113+). No breaking changes — all existing WebGL code works unchanged.
218
+
219
+ ### Setup
220
+
221
+ ```typescript
222
+ // 1. Enable WebGPU support (once, before creating any particle system)
223
+ import { enableWebGPU } from "@newkrok/three-particles/webgpu";
224
+ enableWebGPU();
225
+
226
+ // 2. Create a WebGPU renderer
227
+ import * as THREE from "three/webgpu";
228
+ const renderer = new THREE.WebGPURenderer({ antialias: true });
229
+ await renderer.init();
230
+
231
+ // 3. Create a GPU-accelerated particle system
232
+ import { createParticleSystem, SimulationBackend } from "@newkrok/three-particles";
233
+ const system = createParticleSystem({
234
+ simulationBackend: SimulationBackend.AUTO, // GPU if WebGPU available, else CPU
235
+ maxParticles: 100000,
236
+ // ... rest of config (same API as CPU)
237
+ });
238
+
239
+ scene.add(system.instance);
240
+
241
+ // 4. In your render loop — dispatch compute before rendering
242
+ system.update({ now: performance.now(), delta, elapsed });
243
+ if (system.computeNode) {
244
+ renderer.compute(system.computeNode);
245
+ }
246
+ renderer.render(scene, camera);
247
+ ```
248
+
249
+ For fine-grained control, you can also use `registerTSLMaterialFactory()` to selectively register individual WebGPU functions — see the full API reference.
250
+
251
+ ### SimulationBackend
252
+
253
+ | Value | WebGPU Renderer | WebGL Renderer |
254
+ |-------|----------------|----------------|
255
+ | `AUTO` (default) | GPU compute | CPU (JavaScript) |
256
+ | `CPU` | CPU (JavaScript) | CPU (JavaScript) |
257
+ | `GPU` | GPU compute | CPU (fallback) |
258
+
259
+ ### What runs on GPU
260
+
261
+ - Core physics: gravity, velocity integration, position update, lifetime tracking
262
+ - All 7 modifiers: size/opacity/color over lifetime, rotation, linear/orbital velocity, noise (3D simplex FBM)
263
+ - Force fields: point/directional with falloff (up to 16 per system)
264
+ - Collision planes: kill/clamp/bounce with dampen and lifetime loss
265
+ - Curves: baked into 256-sample Float32Array lookup tables (<0.4% error)
266
+
267
+ ### What stays on CPU
268
+
269
+ - Emission (particle activation, burst scheduling, rate-over-distance)
270
+ - Sub-emitter spawning (birth/death triggers; sub-emitters are forced to CPU backend)
271
+ - Configuration changes (`updateConfig`)
272
+ - Trail renderer (`RendererType.TRAIL` always uses CPU simulation)
273
+
274
+ ### Compute dispatch
275
+
276
+ The returned `ParticleSystem` exposes `computeNode` (non-null when GPU compute is active). This must be dispatched every frame **before** `renderer.render()`:
277
+
278
+ ```typescript
279
+ if (system.computeNode) {
280
+ renderer.compute(system.computeNode);
281
+ }
282
+ ```
283
+
284
+ ### Fallback behavior
285
+
286
+ - If `enableWebGPU()` not called (or no TSL factory registered): uses GLSL shaders (WebGL path)
287
+ - If `GPU` requested but renderer lacks compute: silently falls back to CPU
288
+ - Same `ParticleSystemConfig` works on both backends — no config changes needed
289
+
201
290
  ## Usage with React Three Fiber
202
291
 
203
292
  No wrapper package needed — use hooks directly:
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@newkrok/three-particles",
3
- "version": "2.15.1",
3
+ "version": "2.16.0",
4
4
  "type": "module",
5
5
  "description": "Three.js-based high-performance particle system library designed for creating visually stunning particle effects with ease. Perfect for game developers and 3D applications.",
6
6
  "main": "./dist/index.js",
@@ -8,6 +8,7 @@
8
8
  "types": "./dist/index.d.ts",
9
9
  "files": [
10
10
  "dist/",
11
+ "webgpu.d.ts",
11
12
  "README.md",
12
13
  "LICENSE",
13
14
  "llms.txt",
@@ -19,6 +20,10 @@
19
20
  ".": {
20
21
  "import": "./dist/index.js",
21
22
  "types": "./dist/index.d.ts"
23
+ },
24
+ "./webgpu": {
25
+ "import": "./dist/webgpu.js",
26
+ "types": "./webgpu.d.ts"
22
27
  }
23
28
  },
24
29
  "repository": {
@@ -73,15 +78,15 @@
73
78
  "three": "^0.182.0"
74
79
  },
75
80
  "devDependencies": {
76
- "@babel/preset-env": "^7.29.0",
81
+ "@babel/preset-env": "^7.29.2",
77
82
  "@babel/preset-typescript": "^7.28.5",
78
83
  "@commitlint/cli": "^20.5.0",
79
84
  "@commitlint/config-conventional": "^20.5.0",
80
85
  "@types/jest": "^30.0.0",
81
- "@types/node": "^25.5.0",
86
+ "@types/node": "^25.6.0",
82
87
  "@types/three": "^0.183.1",
83
- "@typescript-eslint/eslint-plugin": "^8.57.1",
84
- "@typescript-eslint/parser": "^8.57.1",
88
+ "@typescript-eslint/eslint-plugin": "^8.58.2",
89
+ "@typescript-eslint/parser": "^8.58.2",
85
90
  "babel-jest": "^30.3.0",
86
91
  "eslint": "^9.39.2",
87
92
  "eslint-config-prettier": "^10.1.8",
@@ -90,14 +95,14 @@
90
95
  "husky": "^9.1.7",
91
96
  "jest": "^30.3.0",
92
97
  "madge": "^8.0.0",
93
- "prettier": "^3.8.1",
98
+ "prettier": "^3.8.3",
94
99
  "rimraf": "^6.1.3",
95
- "ts-jest": "^29.4.6",
100
+ "ts-jest": "^29.4.9",
96
101
  "ts-node": "^10.9.2",
97
102
  "tsup": "^8.5.1",
98
- "typedoc": "^0.28.17",
103
+ "typedoc": "^0.28.19",
99
104
  "typescript": "^5.9.3",
100
- "webpack": "^5.105.4",
101
- "webpack-cli": "^7.0.1"
105
+ "webpack": "^5.106.2",
106
+ "webpack-cli": "^7.0.2"
102
107
  }
103
108
  }
package/webgpu.d.ts ADDED
@@ -0,0 +1,88 @@
1
+ /**
2
+ * WebGPU entry point type declarations for @newkrok/three-particles/webgpu.
3
+ *
4
+ * Hand-written because automatic DTS generation fails on TSL node types
5
+ * (Three.js TSL Fn return types resolve to `unknown` in the type system).
6
+ */
7
+ import type { Material, Blending } from 'three';
8
+ import type { RendererType } from '@newkrok/three-particles';
9
+
10
+ /** Renderer configuration for material creation. */
11
+ export interface RendererConfig {
12
+ transparent: boolean;
13
+ blending: Blending;
14
+ depthTest: boolean;
15
+ depthWrite: boolean;
16
+ }
17
+
18
+ /** Creates a TSL NodeMaterial for the main particle system (non-trail). */
19
+ export declare function createTSLParticleMaterial(
20
+ rendererType: RendererType,
21
+ sharedUniforms: Record<string, { value: unknown }>,
22
+ rendererConfig: RendererConfig,
23
+ gpuCompute?: boolean
24
+ ): Material;
25
+
26
+ /** Creates a TSL NodeMaterial for the trail ribbon renderer. */
27
+ export declare function createTSLTrailMaterial(
28
+ trailUniforms: Record<string, { value: unknown }>,
29
+ rendererConfig: RendererConfig
30
+ ): Material;
31
+
32
+ /** Creates the GPU compute pipeline for particle simulation. */
33
+ export declare function createComputePipeline(
34
+ maxParticles: number,
35
+ instanced: boolean,
36
+ normalizedConfig: unknown,
37
+ particleSystemId: number,
38
+ forceFieldCount: number,
39
+ collisionPlaneCount?: number
40
+ ): unknown;
41
+
42
+ /** Writes init data for a newly emitted particle into modifier storage buffers. */
43
+ export declare function writeParticleToModifierBuffers(
44
+ buffers: unknown,
45
+ index: number,
46
+ data: Record<string, unknown>
47
+ ): void;
48
+
49
+ /** Deactivates a particle in the modifier storage buffers. */
50
+ export declare function deactivateParticleInModifierBuffers(
51
+ buffers: unknown,
52
+ index: number
53
+ ): void;
54
+
55
+ /** Flushes pending init data to the GPU. Call once per frame before compute dispatch. */
56
+ export declare function flushEmitQueue(buffers: unknown): number;
57
+
58
+ /** Registers the curve data length for a buffer. Called once during pipeline creation. */
59
+ export declare function registerCurveDataLength(
60
+ buffers: unknown,
61
+ curveDataLength: number
62
+ ): void;
63
+
64
+ /** Packs force field configs into a flat Float32Array for GPU upload. */
65
+ export declare function encodeForceFieldsForGPU(
66
+ forceFields: ReadonlyArray<unknown>,
67
+ particleSystemId: number,
68
+ systemLifetimePercentage: number
69
+ ): Float32Array;
70
+
71
+ /** Packs collision plane configs into a flat Float32Array for GPU upload. */
72
+ export declare function encodeCollisionPlanesForGPU(
73
+ planes: ReadonlyArray<unknown>
74
+ ): Float32Array;
75
+
76
+ /**
77
+ * Convenience function that registers all WebGPU TSL material factories
78
+ * and GPU compute helpers in a single call.
79
+ *
80
+ * Call this **once** before creating any particle systems that use WebGPU rendering.
81
+ *
82
+ * @example
83
+ * ```typescript
84
+ * import { enableWebGPU } from '@newkrok/three-particles/webgpu';
85
+ * enableWebGPU();
86
+ * ```
87
+ */
88
+ export declare function enableWebGPU(): void;