npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

hz-particles

v1.4.2

Published

High-performance WebGPU particle engine with compute shaders, GPU instancing, GLB model support, and skeletal animations

Readme

hz-particles

Prompt-generated, real-time WebGPU FX for Three.js and React Three Fiber.

Design particle effects in the HZ editor — by hand or from a prompt — export them as JSON / .hzfx presets, and render them faithfully in your app with the same WebGPU engine the editor uses. What you design is exactly what renders.

npm version license WebGPU

import { HZFaithfulFX } from 'hz-particles/r3f';
import firePreset from './fire.json';

export function Scene() {
  return <HZFaithfulFX preset={firePreset} position={[0, 1, 0]} />;
}
  • Prompt FX generator / editor: https://particles.hole.zone/editor
  • Gallery (live WebGPU previews): https://particles.hole.zone/discover
  • Docs: https://particles.hole.zone/docs
  • NPM: https://www.npmjs.com/package/hz-particles

Why hz-particles?

Most AI/VFX tools generate videos or spritesheets. hz-particles generates real-time, editable FX presets that run inside your Three.js / R3F scene.

  • Not a video — effects are simulated in real time, on the GPU, every frame.
  • Not a screenshot — presets are editable JSON / .hzfx files you can re-tune or re-prompt.
  • Not a separate renderer — the R3F component renders the same engine as the editor, so there's no "looked great in the editor, renders wrong in-game" gap.
  • Game-ready — moving emitters, trails, depth occlusion, bloom, GLB-mesh particles.

Text-to-realtime-FX, not text-to-video. The HZ editor turns a prompt into an editable preset; this package is the runtime that renders it in your app.

The full pipeline:

HZ editor (manual or prompt)  →  preset (JSON / .hzfx)  →  faithful WebGPU runtime  →  Three.js / R3F

Features

  • WebGPU Compute Shaders — GPU-accelerated particle physics simulation
  • GPU Instancing — efficient rendering of thousands of particles
  • Engine-faithful R3F componentHZFaithfulFX runs the real overlay inline on three's WebGPURenderer: shader shapes, noise, per-system blending, multi-pass bloom, faithful trails, depth occlusion. Editor parity by construction.
  • Faithful trails & moving emitters — comet trails that follow an arbitrary path (ball trails)
  • GLB model support — use 3D models as particle shapes, with automatic texture extraction
  • Skeletal animations — animated GLB models with full animation control
  • 20+ emitter shapes — volumes & surfaces: sphere, cube, cylinder, cone, torus, capsule, frustum, hemisphere, disc, annulus, arc, spiral, polygon, cubeSurface, sphereSurface, boxFrame, and more
  • Real-time physics — gravity, attractors, drag, velocity, lifetime
  • Particle orientation modesbillboard (fixed/random/velocity-aligned), oriented (a fixed world angle via Euler orientX/Y/Z), and cylindrical (upright, faces the camera horizontally)
  • Keyframe animation — animate per-system parameters over time with curves + easing (keyframes + keyframesEnabled), driven by the unified clock; per-emitter startAt sequencing
  • Serialized presets — self-contained .hzfx binary package (textures + GLB inlined) or plain JSON
  • 3D object support — static 3D objects alongside particle systems

Requirements

A WebGPU-capable browser is required:

  • Chrome / Edge 113+ (stable)
  • Firefox Nightly (experimental)
  • Safari Technology Preview / Safari 18+ (experimental)

Check browser support: caniuse.com/webgpu

Installation

npm install hz-particles
// React Three Fiber (recommended)
import { HZFaithfulFX } from 'hz-particles/r3f';

// Standalone WebGPU / overlay
import { initHzFxOverlay, ParticleSystemManager } from 'hz-particles';

For R3F usage you also need the peers: react, three (with its WebGPU backend), and @react-three/fiber.

Usage modes

Pick the entry point that matches how much control you need:

1. Drop-in R3F FX — <HZFaithfulFX />

The fastest path. Give it a preset, drop it in your scene, done. Engine-faithful by construction.

2. Overlay / inline renderer — initHzFxOverlay()

For non-React apps, custom render loops, post-processing pipelines, or when you want to host many coexisting effects and drive the camera yourself. Same faithful engine, full control.

3. Low-level particle engine — ParticleSystemManager / ParticleSystem

Build your own engine on top of the raw compute + render pipelines.

Quick Start (React Three Fiber)

<HZFaithfulFX> runs the real engine inline on three's WebGPURenderer: shader-drawn shapes, noise, per-system blending, multi-pass bloom, faithful trails, and depth occlusion. Pass a position for a static FX, or a positionRef for a moving one (e.g. a ball trail) — the overlay pulls the ref each frame.

Your R3F Canvas must use a WebGPU renderer. With @react-three/fiber v9 and three/webgpu:

import { Canvas } from '@react-three/fiber';
import * as THREE from 'three/webgpu';
import { useRef } from 'react';
import { HZFaithfulFX } from 'hz-particles/r3f';
import firePreset from './fire.json';
import trailPreset from './trail.json';

export function App() {
  const ballRef = useRef<THREE.Vector3>(null); // updated by your app each frame

  return (
    <Canvas
      // Provide a WebGPURenderer instead of the default WebGLRenderer:
      gl={async (props) => {
        const renderer = new THREE.WebGPURenderer(props as any);
        await renderer.init();
        return renderer;
      }}
      camera={{ position: [0, 2, 6], fov: 50 }}
    >
      <HZFaithfulFX preset={firePreset} position={[0, 1, 0]} />        {/* static FX */}
      <HZFaithfulFX preset={trailPreset} positionRef={ballRef} scale={1.5} /> {/* ball trail */}
    </Canvas>
  );
}

Important integration note — <HZFaithfulFX> drives rendering: its useFrame calls gl.render itself, so R3F stops auto-rendering. This is what guarantees the FX composites on top of your scene with correct depth occlusion.

  • Use <HZFaithfulFX> when it can own the frame (the common case).
  • If your host owns its own render loop (custom RAF, a post-processing / EffectComposer pipeline), use initHzFxOverlay() directly instead so you stay in control of when the scene and the FX render.

Re-trigger an effect by remounting it (change its key).

Loading .hzfx presets

The preset prop is a plain SceneData object. JSON presets can be imported directly (above). For the binary .hzfx package, fetch it with fetchPreset (auto-detects .hzfx vs JSON):

import { useEffect, useState } from 'react';
import { fetchPreset } from 'hz-particles/r3f';
import { HZFaithfulFX } from 'hz-particles/r3f';

function Fire() {
  const [preset, setPreset] = useState(null);
  useEffect(() => { fetchPreset('/fx/fire.hzfx').then(setPreset); }, []);
  return preset ? <HZFaithfulFX preset={preset} position={[0, 1, 0]} /> : null;
}

Props reference

| Prop | Type | Default | Description | |------|------|---------|-------------| | preset | SceneData | — | Particle preset (JSON object exported from the editor) | | position | [number, number, number] | [0, 0, 0] | Static emitter world position (when positionRef is omitted) | | positionRef | RefObject<Vector3 \| null> | — | Moving emitter (e.g. a ball trail): the overlay reads this ref every frame | | scale | number | 1 | Uniform scale applied to the preset (sizes, speeds, emission, trail) | | active | boolean | true | When false, a moving emitter pauses (no new trail) | | renderPriority | number | 1 | useFrame priority. This component drives rendering, so keep it > 0 | | noOcclusion | boolean | false | Skip scene-depth occlusion (particles never hidden behind geometry) |

The hz-particles/r3f entry also exports HZTrailRibbon (a lightweight ribbon-trail mesh driven by a positionRef) and the helpers scalePreset, computeParticleSize, computeParticleOpacity.

Engine-faithful overlay

initHzFxOverlay() is the same render loop the HZ previews and editor use — shader-drawn shapes, per-system blending, noise distortion, GLB-mesh particles + animation, multi-pass bloom, and faithful trails. It's the recommended way to drop HZ effects into any app (vanilla JS, TypeScript, or a React app with its own render loop — one core, no per-framework reimplementation).

import { initHzFxOverlay, makeThreeSceneDepth } from 'hz-particles';

// OVERLAY mode: pass a canvas — the overlay owns its WebGPU context and renders on a
// transparent background. Stack it above your scene with pointer-events: none.
const fx = await initHzFxOverlay(canvas);
await fx.loadPreset(preset);                       // or fx.setEmitters([{ preset, position }])

function frame(dt) {
  fx.setCamera(proj, view, [cx, cy, cz]);          // column-major matrices + camera world pos
  fx.render(dt);
}

// INLINE mode (share your own WebGPU renderer, e.g. three's WebGPURenderer):
const fx2 = await initHzFxOverlay(
  { device, context, canvas },
  { getSceneDepth: makeThreeSceneDepth(renderer) } // one-line occlusion behind your geometry
);
// each frame, AFTER renderer.render(scene, camera): fx2.setCamera(...); fx2.render(dt);

Coexisting groups & moving emitters — one overlay hosts many FX at once. addEmitter(preset, pos) adds a static group; addMovingEmitter(preset, { getPosition }) adds a moving one (e.g. a ball trail) whose comet trail follows the path you supply (the overlay pulls getPosition() each frame; return null to pause). Both return { remove() }.

| Method | Description | | --- | --- | | setCamera(proj, view, pos) / setCameraMVP(mvp, pos) | Drive the camera (column-major). | | loadPreset(preset, pos?) / setEmitters([...]) | (Re)build emitters. | | addEmitter(preset, pos?){ remove } | Add a STATIC group that coexists with others. | | addMovingEmitter(preset, { getPosition }){ setPosition, remove } | Add a MOVING group (ball trail). | | render(dt) | Simulate + render one frame (inline: call after your scene render). | | resize() | Recreate render textures (also auto-detected from the canvas). | | clearCaches() | Drop cached bind groups after a replaceSystems/addSystems that reuses ids. | | trackHistoryGroup(ids, getPosition){ remove } | Give existing manager systems a moving trail (history only). | | destroy() | Release GPU resources. |

Options: { getSceneDepth, autoRespawn, manager }. Pass manager to share an existing ParticleSystemManager so your app keeps owning it while the overlay is the single render path.

Advanced: standalone WebGPU engine

If you want to build your own renderer on the raw engine (no overlay, no R3F), drive the ParticleSystemManager / ParticleSystem directly:

import { initWebGPU, ParticleSystemManager } from 'hz-particles';

// 1. Setup WebGPU context
const canvas = document.getElementById('webgpu-canvas');
canvas.width = 800;
canvas.height = 600;
const { device, context, format } = await initWebGPU(canvas);

// 2. Create manager + a particle system
const manager = new ParticleSystemManager(device);
manager.createParticleSystem({ maxParticles: 10000, particleCount: 1000 });

// 3. Initialize the compute pipeline
const system = manager.getActiveSystem();
await system.initComputePipeline(device);

// 4. Render loop
let lastTime = performance.now();
function render() {
  const now = performance.now();
  const dt = (now - lastTime) / 1000;
  lastTime = now;

  manager.updateAllSystems(dt);

  const encoder = device.createCommandEncoder();
  const pass = encoder.beginRenderPass({
    colorAttachments: [{
      view: context.getCurrentTexture().createView(),
      clearValue: { r: 0, g: 0, b: 0, a: 1 },
      loadOp: 'clear',
      storeOp: 'store',
    }],
  });
  system.render(pass);
  pass.end();
  device.queue.submit([encoder.finish()]);

  requestAnimationFrame(render);
}
render();

Preset configuration

Presets are plain SceneData. A few key per-system fields:

| Field | Type | Description | |-------|------|-------------| | particleCount | number | Number of active particles | | lifetime | number | Particle lifetime in seconds | | emissionRate | number | Particles emitted per second | | 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 | | particleSize | number | Base size of each particle | | colors | string[] | Hex colors interpolated over lifetime | | fadeIn | number | Opacity fade-in duration (0–1, fraction of lifetime) | | fadeOut | number | Opacity fade-out start (0–1, fraction of lifetime) | | bloom | boolean | Enable bloom glow on particles | | gravity | number | Gravity strength applied to particles | | damping | number | Velocity damping factor (0 = no drag, 1 = full stop) | | rotationMode | string | Particle orientation: fixed / random / velocity (billboard), oriented (fixed world angle), cylindrical (upright, camera-facing) | | orientX / orientY / orientZ | number | Euler angles (radians) for rotationMode: 'oriented' | | keyframesEnabled | boolean | Enable keyframe animation of system parameters over time | | keyframes | object | Per-parameter keyframe tracks (value + curve/easing) applied via the clock | | startAt | number | Delay (seconds) before this emitter starts, for relative sequencing | | emissionTrailEnabled | boolean | Enable particle trail rendering | | emissionTrailDuration | number | How long trail segments persist (seconds) | | emissionTrailWidth | number | Width of the emission trail |

serializeSystemConfig(config) is the single source of truth for the full set of serializable fields (used by saveScene and the editor's code export).

API reference

initWebGPU(canvas)

Convenience helper to initialize a WebGPU context and device.

async function initWebGPU(canvas: HTMLCanvasElement): Promise<{
  device: GPUDevice,
  context: GPUCanvasContext,
  format: GPUTextureFormat,
  canvas: HTMLCanvasElement
}>

Set canvas.width / canvas.height before calling. Throws if WebGPU is unsupported.

ParticleSystem

Core particle system class managing simulation and rendering.

class ParticleSystem {
  constructor(device: GPUDevice, config?: object)
}

Config: maxParticles (default 10000), particleCount (default 100).

| Method | Description | |--------|-------------| | initComputePipeline(device) | Initialize GPU compute and render pipelines. Call before rendering. | | setTexture(imageBitmap) | Set particle texture from an ImageBitmap. | | setGLBModel(arrayBuffer) | Use GLB model geometry as particle shape (auto-extracts textures). | | updateParticles(deltaTime) | Execute a physics simulation step on the GPU. | | spawnParticles() | Emit new particles according to emitter configuration. | | setGravity(value) | Set gravity strength (default 9.8). | | setAttractor(strength, position) | Set an attractor point [x, y, z]. | | render(renderPass) | Render particles to the current render pass. |

ParticleSystemManager

Manages multiple particle systems within a single scene.

class ParticleSystemManager {
  constructor(device: GPUDevice)
}

| Method | Description | |--------|-------------| | createParticleSystem(config?) | Create a new particle system. Returns system ID. | | getActiveSystem() | Get the currently active ParticleSystem. | | getActiveConfig() | Get the active system configuration object. | | setActiveSystem(index) | Switch active system by index. | | removeSystem(index) | Remove a system by index. | | updateAllSystems(deltaTime) | Update physics for all systems. | | getSystemsList() | List all systems (name, id, index, isActive). | | duplicateActiveSystem() | Clone the active system. Returns new system ID. | | replaceSystems(sceneData) | Load a scene from serialized data. |

parseGLB(arrayBuffer)

Parse GLB binary and extract geometry.

async function parseGLB(arrayBuffer: ArrayBuffer): Promise<{
  positions: Float32Array,
  normals: Float32Array,
  indices: Uint16Array | Uint32Array,
  texCoords: Float32Array | null,
  vertexCount: number,
  indexCount: number,
  animationData: object | null,
  hasBaseColorTexture: boolean
}>

GLBAnimator

Skeletal animation playback for animated GLB models.

class GLBAnimator {
  constructor(animationData: object)
  currentTime: number
  playing: boolean
  speed: number   // default 1.0
  loop: boolean   // default true
}

| Method | Description | |--------|-------------| | setRestPose(positions, normals) | Set the T-pose/bind pose. | | update(deltaTime) | Advance animation. Returns { positions, normals, changed }. | | setAnimation(index) | Switch to animation clip by index. | | getAnimationNames() | List available animation clip names. |

GLB models & animation

import { parseGLB, GLBAnimator } from 'hz-particles';

const arrayBuffer = await (await fetch('model.glb')).arrayBuffer();
const glbData = await parseGLB(arrayBuffer);

await system.setGLBModel(arrayBuffer); // use as particle shape

if (glbData.animationData) {
  const animator = new GLBAnimator(glbData.animationData);
  animator.setRestPose(glbData.positions, glbData.normals);
  animator.playing = true;

  // in the render loop:
  const { positions, normals, changed } = animator.update(deltaTime);
  if (changed) { /* push updated geometry to the system */ }
}

Scene save / load

import { saveScene, loadScene } from 'hz-particles';

// Save (downloads a self-contained .hzfx package; Alt-click → JSON):
saveButton.addEventListener('click', () => saveScene(manager));

// Load from a file input (.hzfx or JSON, auto-detected):
fileInput.addEventListener('change', async (e) => {
  if (await loadScene(e, manager)) console.log('Scene loaded');
});

TypeScript

Type declarations are shipped — the package's types entry points at dist-lib/hz-particles.d.ts (and dist-lib/hz-particles-r3f.d.ts for the hz-particles/r3f subpath), so imports are fully typed out of the box:

import { initHzFxOverlay, ParticleSystemManager, serializeSystemConfig } from 'hz-particles';
import { HZFaithfulFX } from 'hz-particles/r3f';

Secondary exports

Also exported for advanced usage:

  • fetchPreset(url) — load a preset from a URL (.hzfx or JSON, auto-detected)
  • packHZFX / unpackHZFX / isHZFX — build/read the .hzfx binary package format
  • serializeSystemConfig(config) — single source of truth for a system's serializable fields
  • saveScene(manager) / loadScene(event) — export/import a scene (.hzfx or JSON)
  • ParticleEmitter — emission-shape configuration
  • ParticlePhysics — physics parameter management
  • ParticleTextureManager — texture loading utilities
  • Objects3DManager — 3D object scene management
  • extractGLBTexture(arrayBuffer) — extract base color texture from a GLB
  • Shader / geometry / pipeline helpers — low-level WGSL and pipeline construction

Online editor

Generate effects from a prompt — or build them by hand — in the HZ editor, then export a preset and render it with this package.

  • Hosted editor & prompt FX generator: https://particles.hole.zone/editor
  • Browse the community gallery (live previews): https://particles.hole.zone/discover

The editor provides real-time configuration, emitter-shape tuning, GLB import + animation control, a preset library, and JSON / .hzfx export.

Contributing

  1. Fork the repository
  2. Create a feature branch (git checkout -b feature/amazing-feature)
  3. Commit your changes
  4. Push and open a Pull Request

License

MIT License — see LICENSE.

Copyright (c) 2025-2026 HZ