hz-particles 1.0.15 → 1.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,59 +1,239 @@
1
1
  # hz-particles
2
2
 
3
+ **Prompt-generated, real-time WebGPU FX for Three.js and React Three Fiber.**
4
+
5
+ Design particle effects in the HZ editor — by hand or **from a prompt** — export them as
6
+ `JSON` / `.hzfx` presets, and render them **faithfully** in your app with the *same* WebGPU
7
+ engine the editor uses. What you design is exactly what renders.
8
+
3
9
  [![npm version](https://img.shields.io/npm/v/hz-particles.svg)](https://www.npmjs.com/package/hz-particles)
4
10
  [![license](https://img.shields.io/npm/l/hz-particles.svg)](https://github.com/jguyet/particle-system/blob/main/LICENSE)
5
11
  [![WebGPU](https://img.shields.io/badge/WebGPU-required-blue.svg)](https://caniuse.com/webgpu)
6
12
 
7
- A high-performance WebGPU particle engine for the web. Use it as a standalone WebGPU renderer or as a plug-and-play React Three Fiber component.
13
+ ```tsx
14
+ import { HZFaithfulFX } from 'hz-particles/r3f';
15
+ import firePreset from './fire.json';
16
+
17
+ export function Scene() {
18
+ return <HZFaithfulFX preset={firePreset} position={[0, 1, 0]} />;
19
+ }
20
+ ```
21
+
22
+ <!-- TODO: fill in the real public URLs (see PR / message) -->
23
+ - **Prompt FX generator / editor:** https://YOUR-SITE
24
+ - **Live demo:** https://YOUR-SITE/demo
25
+ - **Docs:** https://YOUR-SITE/docs
26
+ - **NPM:** https://www.npmjs.com/package/hz-particles
27
+
28
+ ## Why hz-particles?
29
+
30
+ Most AI/VFX tools generate **videos** or **spritesheets**. hz-particles generates **real-time,
31
+ editable FX presets** that run inside your Three.js / R3F scene.
32
+
33
+ - **Not a video** — effects are simulated in real time, on the GPU, every frame.
34
+ - **Not a screenshot** — presets are editable `JSON` / `.hzfx` files you can re-tune or re-prompt.
35
+ - **Not a separate renderer** — the R3F component renders the *same engine* as the editor, so
36
+ there's no "looked great in the editor, renders wrong in-game" gap.
37
+ - **Game-ready** — moving emitters, trails, depth occlusion, bloom, GLB-mesh particles.
38
+
39
+ > **Text-to-realtime-FX, not text-to-video.** The HZ editor turns a prompt into an editable
40
+ > preset; this package is the runtime that renders it in your app.
41
+
42
+ The full pipeline:
43
+
44
+ ```
45
+ HZ editor (manual or prompt) → preset (JSON / .hzfx) → faithful WebGPU runtime → Three.js / R3F
46
+ ```
8
47
 
9
48
  ## Features
10
49
 
11
50
  - **WebGPU Compute Shaders** — GPU-accelerated particle physics simulation
12
- - **GPU Instancing** — Efficient rendering of thousands of particles
13
- - **GLB Model Support** — Use 3D models as particle shapes with automatic texture extraction
14
- - **Skeletal Animations** Animated GLB models with full animation control
15
- - **Scene Serialization** Save/load particle configurations as JSON
16
- - **Multiple Emitter Shapes** — Sphere, cube, cylinder, circle, square emission patterns
17
- - **Real-time Physics** — Gravity, attractors, drag, velocity, and lifetime management
18
- - **React Three Fiber Component** — Drop-in `HZParticlesFX` component with preset support
19
- - **3D Object Support** — Static 3D objects alongside particle systems
51
+ - **GPU Instancing** — efficient rendering of thousands of particles
52
+ - **Engine-faithful R3F component** — `HZFaithfulFX` runs the real overlay inline on three's
53
+ `WebGPURenderer`: shader shapes, noise, per-system blending, multi-pass bloom, faithful trails,
54
+ depth occlusion. Editor parity by construction.
55
+ - **Faithful trails & moving emitters** — comet trails that follow an arbitrary path (ball trails)
56
+ - **GLB model support** — use 3D models as particle shapes, with automatic texture extraction
57
+ - **Skeletal animations** — animated GLB models with full animation control
58
+ - **20+ emitter shapes** — volumes & surfaces: sphere, cube, cylinder, cone, torus, capsule,
59
+ frustum, hemisphere, disc, annulus, arc, spiral, polygon, `cubeSurface`, `sphereSurface`,
60
+ `boxFrame`, and more
61
+ - **Real-time physics** — gravity, attractors, drag, velocity, lifetime
62
+ - **Serialized presets** — self-contained `.hzfx` binary package (textures + GLB inlined) or plain JSON
63
+ - **3D object support** — static 3D objects alongside particle systems
20
64
 
21
65
  ## Requirements
22
66
 
23
- **WebGPU-compatible browser required:**
24
- - Chrome/Edge 113+ (stable)
67
+ **A WebGPU-capable browser is required:**
68
+ - Chrome / Edge 113+ (stable)
25
69
  - Firefox Nightly (experimental)
26
- - Safari Technology Preview (experimental)
70
+ - Safari Technology Preview / Safari 18+ (experimental)
27
71
 
28
72
  Check browser support: [caniuse.com/webgpu](https://caniuse.com/webgpu)
29
73
 
30
74
  ## Installation
31
75
 
32
- ### WebGPU
33
-
34
76
  ```bash
35
77
  npm install hz-particles
36
78
  ```
37
79
 
38
80
  ```javascript
39
- import { initWebGPU, ParticleSystemManager } from 'hz-particles';
81
+ // React Three Fiber (recommended)
82
+ import { HZFaithfulFX } from 'hz-particles/r3f';
83
+
84
+ // Standalone WebGPU / overlay
85
+ import { initHzFxOverlay, ParticleSystemManager } from 'hz-particles';
40
86
  ```
41
87
 
42
- ### React Three Fiber
88
+ For R3F usage you also need the peers: `react`, `three` (with its WebGPU backend), and
89
+ `@react-three/fiber`.
43
90
 
44
- ```bash
45
- npm install hz-particles
91
+ ## Usage modes
92
+
93
+ Pick the entry point that matches how much control you need:
94
+
95
+ ### 1. Drop-in R3F FX — `<HZFaithfulFX />`
96
+ The fastest path. Give it a preset, drop it in your scene, done. Engine-faithful by construction.
97
+
98
+ ### 2. Overlay / inline renderer — `initHzFxOverlay()`
99
+ For non-React apps, custom render loops, post-processing pipelines, or when you want to host many
100
+ coexisting effects and drive the camera yourself. Same faithful engine, full control.
101
+
102
+ ### 3. Low-level particle engine — `ParticleSystemManager` / `ParticleSystem`
103
+ Build your own engine on top of the raw compute + render pipelines.
104
+
105
+ ## Quick Start (React Three Fiber)
106
+
107
+ `<HZFaithfulFX>` runs the **real engine** inline on three's `WebGPURenderer`: shader-drawn shapes,
108
+ noise, per-system blending, multi-pass bloom, faithful trails, and depth occlusion. Pass a
109
+ `position` for a static FX, or a `positionRef` for a moving one (e.g. a ball trail) — the overlay
110
+ pulls the ref each frame.
111
+
112
+ Your R3F `Canvas` must use a **WebGPU** renderer. With `@react-three/fiber` v9 and `three/webgpu`:
113
+
114
+ ```tsx
115
+ import { Canvas } from '@react-three/fiber';
116
+ import * as THREE from 'three/webgpu';
117
+ import { useRef } from 'react';
118
+ import { HZFaithfulFX } from 'hz-particles/r3f';
119
+ import firePreset from './fire.json';
120
+ import trailPreset from './trail.json';
121
+
122
+ export function App() {
123
+ const ballRef = useRef<THREE.Vector3>(null); // updated by your app each frame
124
+
125
+ return (
126
+ <Canvas
127
+ // Provide a WebGPURenderer instead of the default WebGLRenderer:
128
+ gl={async (props) => {
129
+ const renderer = new THREE.WebGPURenderer(props as any);
130
+ await renderer.init();
131
+ return renderer;
132
+ }}
133
+ camera={{ position: [0, 2, 6], fov: 50 }}
134
+ >
135
+ <HZFaithfulFX preset={firePreset} position={[0, 1, 0]} /> {/* static FX */}
136
+ <HZFaithfulFX preset={trailPreset} positionRef={ballRef} scale={1.5} /> {/* ball trail */}
137
+ </Canvas>
138
+ );
139
+ }
140
+ ```
141
+
142
+ > **Important integration note —** `<HZFaithfulFX>` **drives rendering**: its `useFrame` calls
143
+ > `gl.render` itself, so R3F stops auto-rendering. This is what guarantees the FX composites on
144
+ > top of your scene with correct depth occlusion.
145
+ >
146
+ > - Use `<HZFaithfulFX>` when it can own the frame (the common case).
147
+ > - If your host owns its own render loop (custom RAF, a post-processing / EffectComposer
148
+ > pipeline), use [`initHzFxOverlay()`](#engine-faithful-overlay) directly instead so *you* stay
149
+ > in control of when the scene and the FX render.
150
+ >
151
+ > Re-trigger an effect by remounting it (change its `key`).
152
+
153
+ ### Loading `.hzfx` presets
154
+
155
+ The `preset` prop is a plain `SceneData` object. JSON presets can be imported directly (above).
156
+ For the binary `.hzfx` package, fetch it with `fetchPreset` (auto-detects `.hzfx` vs JSON):
157
+
158
+ ```tsx
159
+ import { useEffect, useState } from 'react';
160
+ import { fetchPreset } from 'hz-particles/r3f';
161
+ import { HZFaithfulFX } from 'hz-particles/r3f';
162
+
163
+ function Fire() {
164
+ const [preset, setPreset] = useState(null);
165
+ useEffect(() => { fetchPreset('/fx/fire.hzfx').then(setPreset); }, []);
166
+ return preset ? <HZFaithfulFX preset={preset} position={[0, 1, 0]} /> : null;
167
+ }
46
168
  ```
47
169
 
170
+ ### Props reference
171
+
172
+ | Prop | Type | Default | Description |
173
+ |------|------|---------|-------------|
174
+ | `preset` | `SceneData` | — | Particle preset (JSON object exported from the editor) |
175
+ | `position` | `[number, number, number]` | `[0, 0, 0]` | Static emitter world position (when `positionRef` is omitted) |
176
+ | `positionRef` | `RefObject<Vector3 \| null>` | — | Moving emitter (e.g. a ball trail): the overlay reads this ref every frame |
177
+ | `scale` | `number` | `1` | Uniform scale applied to the preset (sizes, speeds, emission, trail) |
178
+ | `active` | `boolean` | `true` | When false, a moving emitter pauses (no new trail) |
179
+ | `renderPriority` | `number` | `1` | `useFrame` priority. This component drives rendering, so keep it > 0 |
180
+ | `noOcclusion` | `boolean` | `false` | Skip scene-depth occlusion (particles never hidden behind geometry) |
181
+
182
+ The `hz-particles/r3f` entry also exports `HZTrailRibbon` (a lightweight ribbon-trail mesh driven
183
+ by a `positionRef`) and the helpers `scalePreset`, `computeParticleSize`, `computeParticleOpacity`.
184
+
185
+ ## Engine-faithful overlay
186
+
187
+ `initHzFxOverlay()` is **the same render loop the HZ previews and editor use** — shader-drawn
188
+ shapes, per-system blending, noise distortion, GLB-mesh particles + animation, multi-pass bloom,
189
+ and faithful trails. It's the recommended way to drop HZ effects into any app (vanilla JS,
190
+ TypeScript, or a React app with its own render loop — one core, no per-framework reimplementation).
191
+
48
192
  ```javascript
49
- import { HZParticlesFX } from 'hz-particles/r3f';
193
+ import { initHzFxOverlay, makeThreeSceneDepth } from 'hz-particles';
194
+
195
+ // OVERLAY mode: pass a canvas — the overlay owns its WebGPU context and renders on a
196
+ // transparent background. Stack it above your scene with pointer-events: none.
197
+ const fx = await initHzFxOverlay(canvas);
198
+ await fx.loadPreset(preset); // or fx.setEmitters([{ preset, position }])
199
+
200
+ function frame(dt) {
201
+ fx.setCamera(proj, view, [cx, cy, cz]); // column-major matrices + camera world pos
202
+ fx.render(dt);
203
+ }
204
+
205
+ // INLINE mode (share your own WebGPU renderer, e.g. three's WebGPURenderer):
206
+ const fx2 = await initHzFxOverlay(
207
+ { device, context, canvas },
208
+ { getSceneDepth: makeThreeSceneDepth(renderer) } // one-line occlusion behind your geometry
209
+ );
210
+ // each frame, AFTER renderer.render(scene, camera): fx2.setCamera(...); fx2.render(dt);
50
211
  ```
51
212
 
52
- > **Important:** Your R3F `Canvas` must use `WebGPURenderer`. Pass `gl={{ powerPreference: 'high-performance' }}` and configure Three.js to use its WebGPU backend, or use `@react-three/fiber` with a WebGPU-capable renderer setup.
213
+ **Coexisting groups & moving emitters** one overlay hosts many FX at once. `addEmitter(preset, pos)`
214
+ adds a static group; `addMovingEmitter(preset, { getPosition })` adds a moving one (e.g. a ball trail)
215
+ whose comet trail follows the path you supply (the overlay *pulls* `getPosition()` each frame; return
216
+ `null` to pause). Both return `{ remove() }`.
217
+
218
+ | Method | Description |
219
+ | --- | --- |
220
+ | `setCamera(proj, view, pos)` / `setCameraMVP(mvp, pos)` | Drive the camera (column-major). |
221
+ | `loadPreset(preset, pos?)` / `setEmitters([...])` | (Re)build emitters. |
222
+ | `addEmitter(preset, pos?)` → `{ remove }` | Add a STATIC group that coexists with others. |
223
+ | `addMovingEmitter(preset, { getPosition })` → `{ setPosition, remove }` | Add a MOVING group (ball trail). |
224
+ | `render(dt)` | Simulate + render one frame (inline: call after your scene render). |
225
+ | `resize()` | Recreate render textures (also auto-detected from the canvas). |
226
+ | `clearCaches()` | Drop cached bind groups after a `replaceSystems`/`addSystems` that reuses ids. |
227
+ | `trackHistoryGroup(ids, getPosition)` → `{ remove }` | Give existing manager systems a moving trail (history only). |
228
+ | `destroy()` | Release GPU resources. |
229
+
230
+ Options: `{ getSceneDepth, autoRespawn, manager }`. Pass `manager` to **share an existing
231
+ `ParticleSystemManager`** so your app keeps owning it while the overlay is the single render path.
53
232
 
54
- ## Quick Start
233
+ ## Advanced: standalone WebGPU engine
55
234
 
56
- ### WebGPU
235
+ If you want to build your own renderer on the raw engine (no overlay, no R3F), drive the
236
+ `ParticleSystemManager` / `ParticleSystem` directly:
57
237
 
58
238
  ```javascript
59
239
  import { initWebGPU, ParticleSystemManager } from 'hz-particles';
@@ -64,31 +244,25 @@ canvas.width = 800;
64
244
  canvas.height = 600;
65
245
  const { device, context, format } = await initWebGPU(canvas);
66
246
 
67
- // 2. Create particle system manager
247
+ // 2. Create manager + a particle system
68
248
  const manager = new ParticleSystemManager(device);
249
+ manager.createParticleSystem({ maxParticles: 10000, particleCount: 1000 });
69
250
 
70
- // 3. Create a particle system
71
- const systemId = manager.createParticleSystem({
72
- maxParticles: 10000,
73
- particleCount: 1000,
74
- });
75
-
76
- // 4. Initialize compute pipeline
251
+ // 3. Initialize the compute pipeline
77
252
  const system = manager.getActiveSystem();
78
253
  await system.initComputePipeline(device);
79
254
 
80
- // 5. Render loop
255
+ // 4. Render loop
81
256
  let lastTime = performance.now();
82
-
83
257
  function render() {
84
- const currentTime = performance.now();
85
- const deltaTime = (currentTime - lastTime) / 1000;
86
- lastTime = currentTime;
258
+ const now = performance.now();
259
+ const dt = (now - lastTime) / 1000;
260
+ lastTime = now;
87
261
 
88
- manager.updateAllSystems(deltaTime);
262
+ manager.updateAllSystems(dt);
89
263
 
90
- const commandEncoder = device.createCommandEncoder();
91
- const renderPass = commandEncoder.beginRenderPass({
264
+ const encoder = device.createCommandEncoder();
265
+ const pass = encoder.beginRenderPass({
92
266
  colorAttachments: [{
93
267
  view: context.getCurrentTexture().createView(),
94
268
  clearValue: { r: 0, g: 0, b: 0, a: 1 },
@@ -96,106 +270,40 @@ function render() {
96
270
  storeOp: 'store',
97
271
  }],
98
272
  });
99
-
100
- system.render(renderPass);
101
- renderPass.end();
102
- device.queue.submit([commandEncoder.finish()]);
273
+ system.render(pass);
274
+ pass.end();
275
+ device.queue.submit([encoder.finish()]);
103
276
 
104
277
  requestAnimationFrame(render);
105
278
  }
106
-
107
279
  render();
108
280
  ```
109
281
 
110
- ### React Three Fiber
282
+ ## Preset configuration
111
283
 
112
- **Minimal example with a preset file:**
113
-
114
- ```jsx
115
- import { HZParticlesFX } from 'hz-particles/r3f';
116
- import explosionPreset from './presets/explosion.json';
117
-
118
- function Scene() {
119
- return (
120
- <HZParticlesFX
121
- preset={explosionPreset}
122
- position={[0, 1, 0]}
123
- />
124
- );
125
- }
126
- ```
127
-
128
- **Complete example with all key props:**
129
-
130
- ```jsx
131
- import { useRef, useState } from 'react';
132
- import { HZParticlesFX } from 'hz-particles/r3f';
133
- import explosionPreset from './presets/explosion.json';
134
-
135
- function Scene() {
136
- const targetRef = useRef();
137
- const [resetKey, setResetKey] = useState(0);
138
-
139
- return (
140
- <>
141
- <mesh ref={targetRef} position={[0, 1, 0]}>
142
- <boxGeometry />
143
- <meshStandardMaterial />
144
- </mesh>
145
-
146
- <HZParticlesFX
147
- preset={explosionPreset}
148
- positionRef={targetRef}
149
- scale={1.5}
150
- resetKey={resetKey}
151
- onComplete={() => console.log('effect finished')}
152
- />
153
-
154
- <button onClick={() => setResetKey(k => k + 1)}>
155
- Replay
156
- </button>
157
- </>
158
- );
159
- }
160
- ```
161
-
162
- ## Props Reference
163
-
164
- The `HZParticlesFX` component accepts the following props:
165
-
166
- | Prop | Type | Default | Description |
167
- |------|------|---------|-------------|
168
- | `preset` | `SceneData \| string` | — | Particle preset — pass a JSON object or a URL/path to a JSON file |
169
- | `position` | `[number, number, number]` | `[0, 0, 0]` | Static world-space position of the effect |
170
- | `positionRef` | `React.RefObject<Object3D>` | — | Attach the effect to a scene object; position tracks the ref each frame |
171
- | `autoPlay` | `boolean` | `true` | Start playing immediately when mounted |
172
- | `visible` | `boolean` | `true` | Show or hide the effect without unmounting |
173
- | `scale` | `number` | `1` | Uniform scale applied to the entire effect |
174
- | `resetKey` | `number` | `0` | Increment to reset and respawn the particle system |
175
- | `onComplete` | `() => void` | — | Callback fired when the effect finishes playing |
176
-
177
- ## Preset Configuration
178
-
179
- Presets are plain JSON files that describe a particle scene. Key fields:
284
+ Presets are plain `SceneData`. A few key per-system fields:
180
285
 
181
286
  | Field | Type | Description |
182
287
  |-------|------|-------------|
183
288
  | `particleCount` | `number` | Number of active particles |
184
289
  | `lifetime` | `number` | Particle lifetime in seconds |
185
290
  | `emissionRate` | `number` | Particles emitted per second |
186
- | `emissionShape` | `string` | Emitter shape: `sphere`, `cube`, `cylinder`, `circle`, `square` |
291
+ | `emissionShape` | `string` | Emitter shape (default `cube`). Volumes: `cube`/`box`, `sphere`, `cylinder`, `cone`, `torus`, `capsule`, `frustum`, `hemisphere`, `spiral`. Flat: `circle`, `square`, `rectangle`, `disc`, `annulus`, `arc`, `polygon`, `plain`, `line`. Surfaces: `cubeSurface`, `sphereSurface`, `boxFrame` |
187
292
  | `particleSize` | `number` | Base size of each particle |
188
- | `colors` | `string[]` | Array of hex color values interpolated over lifetime |
293
+ | `colors` | `string[]` | Hex colors interpolated over lifetime |
189
294
  | `fadeIn` | `number` | Opacity fade-in duration (0–1, fraction of lifetime) |
190
295
  | `fadeOut` | `number` | Opacity fade-out start (0–1, fraction of lifetime) |
191
296
  | `bloom` | `boolean` | Enable bloom glow on particles |
192
297
  | `gravity` | `number` | Gravity strength applied to particles |
193
298
  | `damping` | `number` | Velocity damping factor (0 = no drag, 1 = full stop) |
194
299
  | `emissionTrailEnabled` | `boolean` | Enable particle trail rendering |
195
- | `emissionTrailDuration` | `number` | How long trail segments persist in seconds |
300
+ | `emissionTrailDuration` | `number` | How long trail segments persist (seconds) |
196
301
  | `emissionTrailWidth` | `number` | Width of the emission trail |
197
302
 
198
- ## API Reference
303
+ `serializeSystemConfig(config)` is the single source of truth for the full set of serializable
304
+ fields (used by `saveScene` and the editor's code export).
305
+
306
+ ## API reference
199
307
 
200
308
  ### `initWebGPU(canvas)`
201
309
 
@@ -210,14 +318,7 @@ async function initWebGPU(canvas: HTMLCanvasElement): Promise<{
210
318
  }>
211
319
  ```
212
320
 
213
- **Parameters:**
214
- - `canvas` (required) — Canvas element. Set `canvas.width` and `canvas.height` before calling.
215
-
216
- **Returns:** Object with WebGPU device, canvas context, texture format, and canvas element.
217
-
218
- **Throws:** Error if `canvas` is missing or WebGPU is not supported.
219
-
220
- ---
321
+ Set `canvas.width` / `canvas.height` before calling. Throws if WebGPU is unsupported.
221
322
 
222
323
  ### `ParticleSystem`
223
324
 
@@ -229,25 +330,19 @@ class ParticleSystem {
229
330
  }
230
331
  ```
231
332
 
232
- **Config options:**
233
- - `maxParticles` (number, default `10000`) — Maximum particle buffer size
234
- - `particleCount` (number, default `100`) — Initial active particle count
235
-
236
- **Key Methods:**
333
+ **Config:** `maxParticles` (default `10000`), `particleCount` (default `100`).
237
334
 
238
335
  | Method | Description |
239
336
  |--------|-------------|
240
- | `initComputePipeline(device)` | Initialize GPU compute and render pipelines. Must be called before rendering. |
337
+ | `initComputePipeline(device)` | Initialize GPU compute and render pipelines. Call before rendering. |
241
338
  | `setTexture(imageBitmap)` | Set particle texture from an `ImageBitmap`. |
242
- | `setGLBModel(arrayBuffer)` | Use GLB model geometry as particle shape. Automatically extracts textures. |
339
+ | `setGLBModel(arrayBuffer)` | Use GLB model geometry as particle shape (auto-extracts textures). |
243
340
  | `updateParticles(deltaTime)` | Execute a physics simulation step on the GPU. |
244
341
  | `spawnParticles()` | Emit new particles according to emitter configuration. |
245
342
  | `setGravity(value)` | Set gravity strength (default `9.8`). |
246
- | `setAttractor(strength, position)` | Set an attractor point with strength and 3D position `[x, y, z]`. |
343
+ | `setAttractor(strength, position)` | Set an attractor point `[x, y, z]`. |
247
344
  | `render(renderPass)` | Render particles to the current render pass. |
248
345
 
249
- ---
250
-
251
346
  ### `ParticleSystemManager`
252
347
 
253
348
  Manages multiple particle systems within a single scene.
@@ -258,25 +353,21 @@ class ParticleSystemManager {
258
353
  }
259
354
  ```
260
355
 
261
- **Key Methods:**
262
-
263
356
  | Method | Description |
264
357
  |--------|-------------|
265
358
  | `createParticleSystem(config?)` | Create a new particle system. Returns system ID. |
266
- | `getActiveSystem()` | Get the currently active `ParticleSystem` instance. |
359
+ | `getActiveSystem()` | Get the currently active `ParticleSystem`. |
267
360
  | `getActiveConfig()` | Get the active system configuration object. |
268
- | `setActiveSystem(index)` | Switch active system by index. Returns success status. |
269
- | `removeSystem(index)` | Remove a system by index. Returns success status. |
361
+ | `setActiveSystem(index)` | Switch active system by index. |
362
+ | `removeSystem(index)` | Remove a system by index. |
270
363
  | `updateAllSystems(deltaTime)` | Update physics for all systems. |
271
- | `getSystemsList()` | Get list of all systems with `name`, `id`, `index`, `isActive`. |
364
+ | `getSystemsList()` | List all systems (`name`, `id`, `index`, `isActive`). |
272
365
  | `duplicateActiveSystem()` | Clone the active system. Returns new system ID. |
273
- | `replaceSystems(sceneData)` | Load a scene from serialized data. Returns success status. |
274
-
275
- ---
366
+ | `replaceSystems(sceneData)` | Load a scene from serialized data. |
276
367
 
277
368
  ### `parseGLB(arrayBuffer)`
278
369
 
279
- Parse GLB binary format and extract geometry data.
370
+ Parse GLB binary and extract geometry.
280
371
 
281
372
  ```typescript
282
373
  async function parseGLB(arrayBuffer: ArrayBuffer): Promise<{
@@ -291,16 +382,13 @@ async function parseGLB(arrayBuffer: ArrayBuffer): Promise<{
291
382
  }>
292
383
  ```
293
384
 
294
- ---
295
-
296
385
  ### `GLBAnimator`
297
386
 
298
- Handles skeletal animation playback for animated GLB models.
387
+ Skeletal animation playback for animated GLB models.
299
388
 
300
389
  ```typescript
301
390
  class GLBAnimator {
302
391
  constructor(animationData: object)
303
-
304
392
  currentTime: number
305
393
  playing: boolean
306
394
  speed: number // default 1.0
@@ -308,115 +396,95 @@ class GLBAnimator {
308
396
  }
309
397
  ```
310
398
 
311
- **Key Methods:**
312
-
313
399
  | Method | Description |
314
400
  |--------|-------------|
315
- | `setRestPose(positions, normals)` | Set the T-pose/bind pose for animation. |
401
+ | `setRestPose(positions, normals)` | Set the T-pose/bind pose. |
316
402
  | `update(deltaTime)` | Advance animation. Returns `{ positions, normals, changed }`. |
317
403
  | `setAnimation(index)` | Switch to animation clip by index. |
318
- | `getAnimationNames()` | Get list of available animation clip names. |
404
+ | `getAnimationNames()` | List available animation clip names. |
319
405
 
320
- ## GLB Models & Animation
406
+ ## GLB models & animation
321
407
 
322
408
  ```javascript
323
409
  import { parseGLB, GLBAnimator } from 'hz-particles';
324
410
 
325
- // Load GLB file
326
- const response = await fetch('model.glb');
327
- const arrayBuffer = await response.arrayBuffer();
411
+ const arrayBuffer = await (await fetch('model.glb')).arrayBuffer();
328
412
  const glbData = await parseGLB(arrayBuffer);
329
413
 
330
- // Use as particle shape
331
- await system.setGLBModel(arrayBuffer);
414
+ await system.setGLBModel(arrayBuffer); // use as particle shape
332
415
 
333
- // Setup animation (if model has animations)
334
416
  if (glbData.animationData) {
335
417
  const animator = new GLBAnimator(glbData.animationData);
336
418
  animator.setRestPose(glbData.positions, glbData.normals);
337
419
  animator.playing = true;
338
- animator.loop = true;
339
- animator.speed = 1.0;
340
420
 
341
- // In render loop:
421
+ // in the render loop:
342
422
  const { positions, normals, changed } = animator.update(deltaTime);
343
- if (changed) {
344
- // Update particle system with new geometry
345
- // (See full API documentation for geometry update methods)
346
- }
423
+ if (changed) { /* push updated geometry to the system */ }
347
424
  }
348
425
  ```
349
426
 
350
- ## Scene Save/Load
427
+ ## Scene save / load
351
428
 
352
429
  ```javascript
353
430
  import { saveScene, loadScene } from 'hz-particles';
354
431
 
355
- // Save current scene
356
- document.getElementById('save-button').addEventListener('click', () => {
357
- saveScene(manager); // Downloads scene.json
358
- });
432
+ // Save (downloads a self-contained .hzfx package; Alt-click → JSON):
433
+ saveButton.addEventListener('click', () => saveScene(manager));
359
434
 
360
- // Load scene from file input
361
- document.getElementById('file-input').addEventListener('change', async (event) => {
362
- const success = await loadScene(event, manager);
363
- if (success) {
364
- console.log('Scene loaded successfully');
365
- }
435
+ // Load from a file input (.hzfx or JSON, auto-detected):
436
+ fileInput.addEventListener('change', async (e) => {
437
+ if (await loadScene(e, manager)) console.log('Scene loaded');
366
438
  });
367
439
  ```
368
440
 
369
441
  ## TypeScript
370
442
 
371
- Type declarations are not yet included in this package. For TypeScript projects, suppress the import error with:
443
+ Type declarations **are shipped** the package's `types` entry points at
444
+ `dist-lib/hz-particles.d.ts` (and `dist-lib/hz-particles-r3f.d.ts` for the `hz-particles/r3f`
445
+ subpath), so imports are fully typed out of the box:
372
446
 
373
447
  ```typescript
374
- // @ts-ignore
375
- import { ParticleSystem, initWebGPU } from 'hz-particles';
376
- // @ts-ignore
377
- import { HZParticlesFX } from 'hz-particles/r3f';
448
+ import { initHzFxOverlay, ParticleSystemManager, serializeSystemConfig } from 'hz-particles';
449
+ import { HZFaithfulFX } from 'hz-particles/r3f';
378
450
  ```
379
451
 
380
- Community-contributed type definitions are welcome via PR.
381
-
382
- ## Secondary Exports
452
+ ## Secondary exports
383
453
 
384
- The following modules are also exported for advanced usage:
454
+ Also exported for advanced usage:
385
455
 
386
- - **`ParticleEmitter`** — Emission shape configuration (sphere, cube, cylinder, circle, square)
387
- - **`ParticlePhysics`** — Physics simulation parameter management
388
- - **`ParticleTextureManager`** — Texture loading utilities
456
+ - **`fetchPreset(url)`** — load a preset from a URL (`.hzfx` or JSON, auto-detected)
457
+ - **`packHZFX` / `unpackHZFX` / `isHZFX`** — build/read the `.hzfx` binary package format
458
+ - **`serializeSystemConfig(config)`** — single source of truth for a system's serializable fields
459
+ - **`saveScene(manager)` / `loadScene(event)`** — export/import a scene (`.hzfx` or JSON)
460
+ - **`ParticleEmitter`** — emission-shape configuration
461
+ - **`ParticlePhysics`** — physics parameter management
462
+ - **`ParticleTextureManager`** — texture loading utilities
389
463
  - **`Objects3DManager`** — 3D object scene management
390
- - **`saveScene(manager)`** — Export scene as JSON file download
391
- - **`loadScene(event)`**Load scene from file input event
392
- - **`extractGLBTexture(arrayBuffer)`** — Extract base color texture from GLB file
393
- - **Shader exports** — WGSL shader code for compute and rendering pipelines
394
- - **Geometry helpers** — Primitive shape generators (cube, sphere, etc.)
395
- - **Render pipeline creators** — Low-level WebGPU pipeline construction
464
+ - **`extractGLBTexture(arrayBuffer)`** — extract base color texture from a GLB
465
+ - **Shader / geometry / pipeline helpers** low-level WGSL and pipeline construction
396
466
 
397
- ## Online Editor
467
+ ## Online editor
398
468
 
399
- Try the interactive particle editor at `http://localhost:8110/editor` when running the Docker container (`docker-compose up` from the repository root).
469
+ Generate effects from a prompt or build them by hand — in the HZ editor, then export a preset and
470
+ render it with this package.
400
471
 
401
- The editor provides a visual interface for:
402
- - Real-time particle system configuration
403
- - Emitter shape selection and tuning
404
- - GLB model import and animation control
405
- - Scene preset library
406
- - Export/import scene JSON
472
+ <!-- TODO: replace with the public editor URL -->
473
+ - Hosted editor & prompt FX generator: https://YOUR-SITE
474
+ - Local: `http://localhost:8110/editor` (run `docker-compose up` from the repo root)
407
475
 
408
- ## Contributing
476
+ The editor provides real-time configuration, emitter-shape tuning, GLB import + animation control, a
477
+ preset library, and `JSON` / `.hzfx` export.
409
478
 
410
- Contributions are welcome! Please follow these steps:
479
+ ## Contributing
411
480
 
412
481
  1. Fork the repository
413
482
  2. Create a feature branch (`git checkout -b feature/amazing-feature`)
414
- 3. Commit your changes (`git commit -m 'Add amazing feature'`)
415
- 4. Push to the branch (`git push origin feature/amazing-feature`)
416
- 5. Open a Pull Request
483
+ 3. Commit your changes
484
+ 4. Push and open a Pull Request
417
485
 
418
486
  ## License
419
487
 
420
- MIT License - see [LICENSE](LICENSE) file for details.
488
+ MIT License see [LICENSE](LICENSE).
421
489
 
422
490
  Copyright (c) 2025 HZ