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.0.0

Published

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

Readme

hz-particles

npm version license WebGPU

A high-performance WebGPU particle engine for the web.

Features

  • WebGPU Compute Shaders — GPU-accelerated particle physics simulation
  • GPU Instancing — Efficient rendering of thousands of particles
  • GLB Model Support — Use 3D models as particle shapes with automatic texture extraction
  • Skeletal Animations — Animated GLB models with full animation control
  • Scene Serialization — Save/load particle configurations as JSON
  • Multiple Emitter Shapes — Sphere, cube, cylinder, circle, square emission patterns
  • Real-time Physics — Gravity, attractors, drag, velocity, and lifetime management
  • 3D Object Support — Static 3D objects alongside particle systems

Requirements

WebGPU-compatible browser required:

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

Check browser support: caniuse.com/webgpu

Installation

npm install hz-particles

Quick Start

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

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

// 2. Create particle system manager
const manager = new ParticleSystemManager(device);

// 3. Create a particle system
const systemId = manager.createParticleSystem({
  maxParticles: 10000,
  particleCount: 1000,
});

// 4. Get active system and initialize
const system = manager.getActiveSystem();
await system.initComputePipeline(device);

// Optional: Load a texture
const response = await fetch('particle-texture.png');
const blob = await response.blob();
const imageBitmap = await createImageBitmap(blob);
await system.setTexture(imageBitmap);

// 5. Render loop
let lastTime = performance.now();

function render() {
  const currentTime = performance.now();
  const deltaTime = (currentTime - lastTime) / 1000;
  lastTime = currentTime;

  // Update physics
  manager.updateAllSystems(deltaTime);

  // Render
  const commandEncoder = device.createCommandEncoder();
  const renderPass = commandEncoder.beginRenderPass({
    colorAttachments: [{
      view: context.getCurrentTexture().createView(),
      clearValue: { r: 0, g: 0, b: 0, a: 1 },
      loadOp: 'clear',
      storeOp: 'store',
    }],
  });

  system.render(renderPass);
  renderPass.end();
  device.queue.submit([commandEncoder.finish()]);

  requestAnimationFrame(render);
}

render();

React Three Fiber Integration

While hz-particles uses native WebGPU (not Three.js internally), you can integrate it into React Three Fiber applications:

import { useEffect, useRef } from 'react';
import { Canvas } from '@react-three/fiber';
import { initWebGPU, ParticleSystemManager } from 'hz-particles';

function ParticleLayer() {
  const canvasRef = useRef();
  const engineRef = useRef();

  useEffect(() => {
    let animationId;

    async function init() {
      // Create dedicated WebGPU canvas
      const canvas = canvasRef.current;
      const { device, context, format } = await initWebGPU(canvas);

      // Setup particle system
      const manager = new ParticleSystemManager(device);
      manager.createParticleSystem({ particleCount: 1000 });
      const system = manager.getActiveSystem();
      await system.initComputePipeline(device);

      engineRef.current = { device, context, manager, system };

      // Render loop
      let lastTime = performance.now();
      function render() {
        const { device, context, manager, system } = engineRef.current;
        const currentTime = performance.now();
        const deltaTime = (currentTime - lastTime) / 1000;
        lastTime = currentTime;

        manager.updateAllSystems(deltaTime);

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

        animationId = requestAnimationFrame(render);
      }
      render();
    }

    init();

    return () => {
      if (animationId) cancelAnimationFrame(animationId);
    };
  }, []);

  return (
    <canvas
      ref={canvasRef}
      id="webgpu-canvas"
      style={{
        position: 'absolute',
        top: 0,
        left: 0,
        width: '100%',
        height: '100%',
        pointerEvents: 'none',
      }}
    />
  );
}

// Usage in App
function App() {
  return (
    <div style={{ position: 'relative', width: '100vw', height: '100vh' }}>
      {/* R3F scene */}
      <Canvas>
        <ambientLight />
        <mesh>
          <boxGeometry />
          <meshStandardMaterial />
        </mesh>
      </Canvas>

      {/* WebGPU particles overlay */}
      <ParticleLayer />
    </div>
  );
}

API Reference

initWebGPU(canvas?)

Initialize WebGPU context and device.

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

Parameters:

  • canvas (optional) — Canvas element. If omitted, looks for document.getElementById('webgpu-canvas')

Returns: Object with WebGPU device, canvas context, texture format, and canvas element

Throws: Error if WebGPU is not supported


ParticleSystem

Core particle system class managing particle simulation and rendering.

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

Constructor Parameters:

  • device — GPUDevice instance
  • config (optional) — Configuration object:
    • maxParticles (number, default 10000) — Maximum particle buffer size
    • particleCount (number, default 100) — Initial active particle count

Key Methods:

  • async initComputePipeline(device: GPUDevice) — Initialize GPU compute and render pipelines. Must be called before rendering.

  • async setTexture(imageBitmap: ImageBitmap) — Set particle texture from ImageBitmap.

  • async setGLBModel(arrayBuffer: ArrayBuffer) — Use GLB model geometry as particle shape. Automatically extracts textures.

  • updateParticles(deltaTime: number) — Execute physics simulation step on GPU.

  • spawnParticles() — Emit new particles according to emitter configuration.

  • setGravity(value: number) — Set gravity strength (default 9.8).

  • setAttractor(strength: number, position: [number, number, number]) — Set attractor point with strength and 3D position.

  • render(renderPass: GPURenderPassEncoder) — Render particles to the current render pass.


ParticleSystemManager

Manages multiple particle systems within a single scene.

class ParticleSystemManager {
  constructor(device: GPUDevice)
}

Key Methods:

  • createParticleSystem(config?: object): number — Create new particle system. Returns system ID.

  • getActiveSystem(): ParticleSystem | null — Get currently active particle system instance.

  • getActiveConfig(): object | null — Get active system configuration.

  • setActiveSystem(index: number): boolean — Switch active system by index. Returns success status.

  • removeSystem(index: number): boolean — Remove system by index. Returns success status.

  • updateAllSystems(deltaTime: number) — Update physics for all systems.

  • getSystemsList(): Array<{name: string, id: number, index: number, isActive: boolean}> — Get list of all systems with metadata.

  • duplicateActiveSystem(): number — Clone active system. Returns new system ID.

  • async replaceSystems(sceneData: object): boolean — Load scene from serialized data. Returns success status.


parseGLB(arrayBuffer)

Parse GLB binary format and extract geometry data.

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
}>

Parameters:

  • arrayBuffer — GLB file as ArrayBuffer

Returns: Parsed geometry and animation data


GLBAnimator

Handles 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
}

Key Methods:

  • setRestPose(positions: Float32Array, normals: Float32Array) — Set T-pose/bind pose for animation.

  • update(deltaTime: number): { positions: Float32Array, normals: Float32Array, changed: boolean } — Advance animation by deltaTime. Returns deformed geometry and change status.

  • setAnimation(index: number) — Switch to animation clip by index.

  • getAnimationNames(): string[] — Get list of available animation clip names.


Secondary Exports

The following modules are also exported for advanced usage:

  • ParticleEmitter — Emission shape configuration (sphere, cube, cylinder, circle, square)
  • ParticlePhysics — Physics simulation parameter management
  • ParticleTextureManager — Texture loading utilities
  • Objects3DManager — 3D object scene management
  • saveScene(manager) — Export scene as JSON file download
  • loadScene(event) — Load scene from file input event
  • extractGLBTexture(arrayBuffer) — Extract base color texture from GLB file
  • Shader exports — WGSL shader code for compute and rendering pipelines
  • Geometry helpers — Primitive shape generators (cube, sphere, etc.)
  • Render pipeline creators — Low-level WebGPU pipeline construction

GLB Models & Animation

Load and animate 3D models:

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

// Load GLB file
const response = await fetch('model.glb');
const arrayBuffer = await response.arrayBuffer();
const glbData = await parseGLB(arrayBuffer);

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

// Setup animation (if model has animations)
if (glbData.animationData) {
  const animator = new GLBAnimator(glbData.animationData);
  animator.setRestPose(glbData.positions, glbData.normals);
  animator.playing = true;
  animator.loop = true;
  animator.speed = 1.0;

  // In render loop:
  const { positions, normals, changed } = animator.update(deltaTime);
  if (changed) {
    // Update particle system with new geometry
    // (See full API documentation for geometry update methods)
  }
}

Scene Save/Load

Serialize and restore particle configurations:

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

// Save current scene
const button = document.getElementById('save-button');
button.addEventListener('click', () => {
  saveScene(manager);  // Downloads scene.json
});

// Load scene from file input
const input = document.getElementById('file-input');
input.addEventListener('change', async (event) => {
  const success = await loadScene(event, manager);
  if (success) {
    console.log('Scene loaded successfully');
  }
});

Online Editor

Try the interactive particle editor: Online Editor (deployment URL coming soon)

The editor provides a visual interface for:

  • Real-time particle system configuration
  • Emitter shape selection and tuning
  • GLB model import and animation control
  • Scene preset library
  • Export/import scene JSON

TypeScript

Type declarations are not yet included in this package. For TypeScript projects, you can suppress the import error:

// @ts-ignore
import { ParticleSystem, initWebGPU } from 'hz-particles';

Community-contributed type definitions are welcome via PR.

Contributing

Contributions are welcome! Please follow these steps:

  1. Fork the repository
  2. Create a feature branch (git checkout -b feature/amazing-feature)
  3. Commit your changes (git commit -m 'Add amazing feature')
  4. Push to the branch (git push origin feature/amazing-feature)
  5. Open a Pull Request

License

MIT License - see LICENSE file for details.

Copyright (c) 2025 HZ