hz-particles 1.0.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/LICENSE +21 -0
- package/README.md +444 -0
- package/dist-lib/hz-particles.cjs +617 -0
- package/dist-lib/hz-particles.mjs +3315 -0
- package/package.json +49 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 HZ
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,444 @@
|
|
|
1
|
+
# hz-particles
|
|
2
|
+
|
|
3
|
+
[](https://www.npmjs.com/package/hz-particles)
|
|
4
|
+
[](https://github.com/TODO/hz-particles/blob/main/LICENSE)
|
|
5
|
+
[](https://caniuse.com/webgpu)
|
|
6
|
+
|
|
7
|
+
A high-performance WebGPU particle engine for the web.
|
|
8
|
+
|
|
9
|
+
## Features
|
|
10
|
+
|
|
11
|
+
- **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
|
+
- **3D Object Support** — Static 3D objects alongside particle systems
|
|
19
|
+
|
|
20
|
+
## Requirements
|
|
21
|
+
|
|
22
|
+
**WebGPU-compatible browser required:**
|
|
23
|
+
- Chrome/Edge 113+ (stable)
|
|
24
|
+
- Firefox Nightly (experimental)
|
|
25
|
+
- Safari Technology Preview (experimental)
|
|
26
|
+
|
|
27
|
+
Check browser support: [caniuse.com/webgpu](https://caniuse.com/webgpu)
|
|
28
|
+
|
|
29
|
+
## Installation
|
|
30
|
+
|
|
31
|
+
```bash
|
|
32
|
+
npm install hz-particles
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
## Quick Start
|
|
36
|
+
|
|
37
|
+
```javascript
|
|
38
|
+
import { initWebGPU, ParticleSystemManager } from 'hz-particles';
|
|
39
|
+
|
|
40
|
+
// 1. Setup WebGPU context
|
|
41
|
+
const canvas = document.getElementById('webgpu-canvas');
|
|
42
|
+
const { device, context, format } = await initWebGPU(canvas);
|
|
43
|
+
|
|
44
|
+
// 2. Create particle system manager
|
|
45
|
+
const manager = new ParticleSystemManager(device);
|
|
46
|
+
|
|
47
|
+
// 3. Create a particle system
|
|
48
|
+
const systemId = manager.createParticleSystem({
|
|
49
|
+
maxParticles: 10000,
|
|
50
|
+
particleCount: 1000,
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
// 4. Get active system and initialize
|
|
54
|
+
const system = manager.getActiveSystem();
|
|
55
|
+
await system.initComputePipeline(device);
|
|
56
|
+
|
|
57
|
+
// Optional: Load a texture
|
|
58
|
+
const response = await fetch('particle-texture.png');
|
|
59
|
+
const blob = await response.blob();
|
|
60
|
+
const imageBitmap = await createImageBitmap(blob);
|
|
61
|
+
await system.setTexture(imageBitmap);
|
|
62
|
+
|
|
63
|
+
// 5. Render loop
|
|
64
|
+
let lastTime = performance.now();
|
|
65
|
+
|
|
66
|
+
function render() {
|
|
67
|
+
const currentTime = performance.now();
|
|
68
|
+
const deltaTime = (currentTime - lastTime) / 1000;
|
|
69
|
+
lastTime = currentTime;
|
|
70
|
+
|
|
71
|
+
// Update physics
|
|
72
|
+
manager.updateAllSystems(deltaTime);
|
|
73
|
+
|
|
74
|
+
// Render
|
|
75
|
+
const commandEncoder = device.createCommandEncoder();
|
|
76
|
+
const renderPass = commandEncoder.beginRenderPass({
|
|
77
|
+
colorAttachments: [{
|
|
78
|
+
view: context.getCurrentTexture().createView(),
|
|
79
|
+
clearValue: { r: 0, g: 0, b: 0, a: 1 },
|
|
80
|
+
loadOp: 'clear',
|
|
81
|
+
storeOp: 'store',
|
|
82
|
+
}],
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
system.render(renderPass);
|
|
86
|
+
renderPass.end();
|
|
87
|
+
device.queue.submit([commandEncoder.finish()]);
|
|
88
|
+
|
|
89
|
+
requestAnimationFrame(render);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
render();
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
## React Three Fiber Integration
|
|
96
|
+
|
|
97
|
+
While `hz-particles` uses native WebGPU (not Three.js internally), you can integrate it into React Three Fiber applications:
|
|
98
|
+
|
|
99
|
+
```jsx
|
|
100
|
+
import { useEffect, useRef } from 'react';
|
|
101
|
+
import { Canvas } from '@react-three/fiber';
|
|
102
|
+
import { initWebGPU, ParticleSystemManager } from 'hz-particles';
|
|
103
|
+
|
|
104
|
+
function ParticleLayer() {
|
|
105
|
+
const canvasRef = useRef();
|
|
106
|
+
const engineRef = useRef();
|
|
107
|
+
|
|
108
|
+
useEffect(() => {
|
|
109
|
+
let animationId;
|
|
110
|
+
|
|
111
|
+
async function init() {
|
|
112
|
+
// Create dedicated WebGPU canvas
|
|
113
|
+
const canvas = canvasRef.current;
|
|
114
|
+
const { device, context, format } = await initWebGPU(canvas);
|
|
115
|
+
|
|
116
|
+
// Setup particle system
|
|
117
|
+
const manager = new ParticleSystemManager(device);
|
|
118
|
+
manager.createParticleSystem({ particleCount: 1000 });
|
|
119
|
+
const system = manager.getActiveSystem();
|
|
120
|
+
await system.initComputePipeline(device);
|
|
121
|
+
|
|
122
|
+
engineRef.current = { device, context, manager, system };
|
|
123
|
+
|
|
124
|
+
// Render loop
|
|
125
|
+
let lastTime = performance.now();
|
|
126
|
+
function render() {
|
|
127
|
+
const { device, context, manager, system } = engineRef.current;
|
|
128
|
+
const currentTime = performance.now();
|
|
129
|
+
const deltaTime = (currentTime - lastTime) / 1000;
|
|
130
|
+
lastTime = currentTime;
|
|
131
|
+
|
|
132
|
+
manager.updateAllSystems(deltaTime);
|
|
133
|
+
|
|
134
|
+
const commandEncoder = device.createCommandEncoder();
|
|
135
|
+
const renderPass = commandEncoder.beginRenderPass({
|
|
136
|
+
colorAttachments: [{
|
|
137
|
+
view: context.getCurrentTexture().createView(),
|
|
138
|
+
clearValue: { r: 0, g: 0, b: 0, a: 0 }, // Transparent
|
|
139
|
+
loadOp: 'clear',
|
|
140
|
+
storeOp: 'store',
|
|
141
|
+
}],
|
|
142
|
+
});
|
|
143
|
+
system.render(renderPass);
|
|
144
|
+
renderPass.end();
|
|
145
|
+
device.queue.submit([commandEncoder.finish()]);
|
|
146
|
+
|
|
147
|
+
animationId = requestAnimationFrame(render);
|
|
148
|
+
}
|
|
149
|
+
render();
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
init();
|
|
153
|
+
|
|
154
|
+
return () => {
|
|
155
|
+
if (animationId) cancelAnimationFrame(animationId);
|
|
156
|
+
};
|
|
157
|
+
}, []);
|
|
158
|
+
|
|
159
|
+
return (
|
|
160
|
+
<canvas
|
|
161
|
+
ref={canvasRef}
|
|
162
|
+
id="webgpu-canvas"
|
|
163
|
+
style={{
|
|
164
|
+
position: 'absolute',
|
|
165
|
+
top: 0,
|
|
166
|
+
left: 0,
|
|
167
|
+
width: '100%',
|
|
168
|
+
height: '100%',
|
|
169
|
+
pointerEvents: 'none',
|
|
170
|
+
}}
|
|
171
|
+
/>
|
|
172
|
+
);
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
// Usage in App
|
|
176
|
+
function App() {
|
|
177
|
+
return (
|
|
178
|
+
<div style={{ position: 'relative', width: '100vw', height: '100vh' }}>
|
|
179
|
+
{/* R3F scene */}
|
|
180
|
+
<Canvas>
|
|
181
|
+
<ambientLight />
|
|
182
|
+
<mesh>
|
|
183
|
+
<boxGeometry />
|
|
184
|
+
<meshStandardMaterial />
|
|
185
|
+
</mesh>
|
|
186
|
+
</Canvas>
|
|
187
|
+
|
|
188
|
+
{/* WebGPU particles overlay */}
|
|
189
|
+
<ParticleLayer />
|
|
190
|
+
</div>
|
|
191
|
+
);
|
|
192
|
+
}
|
|
193
|
+
```
|
|
194
|
+
|
|
195
|
+
## API Reference
|
|
196
|
+
|
|
197
|
+
### `initWebGPU(canvas?)`
|
|
198
|
+
|
|
199
|
+
Initialize WebGPU context and device.
|
|
200
|
+
|
|
201
|
+
```typescript
|
|
202
|
+
async function initWebGPU(canvas?: HTMLCanvasElement): Promise<{
|
|
203
|
+
device: GPUDevice,
|
|
204
|
+
context: GPUCanvasContext,
|
|
205
|
+
format: GPUTextureFormat,
|
|
206
|
+
canvas: HTMLCanvasElement
|
|
207
|
+
}>
|
|
208
|
+
```
|
|
209
|
+
|
|
210
|
+
**Parameters:**
|
|
211
|
+
- `canvas` (optional) — Canvas element. If omitted, looks for `document.getElementById('webgpu-canvas')`
|
|
212
|
+
|
|
213
|
+
**Returns:** Object with WebGPU device, canvas context, texture format, and canvas element
|
|
214
|
+
|
|
215
|
+
**Throws:** Error if WebGPU is not supported
|
|
216
|
+
|
|
217
|
+
---
|
|
218
|
+
|
|
219
|
+
### `ParticleSystem`
|
|
220
|
+
|
|
221
|
+
Core particle system class managing particle simulation and rendering.
|
|
222
|
+
|
|
223
|
+
```typescript
|
|
224
|
+
class ParticleSystem {
|
|
225
|
+
constructor(device: GPUDevice, config?: object)
|
|
226
|
+
}
|
|
227
|
+
```
|
|
228
|
+
|
|
229
|
+
**Constructor Parameters:**
|
|
230
|
+
- `device` — GPUDevice instance
|
|
231
|
+
- `config` (optional) — Configuration object:
|
|
232
|
+
- `maxParticles` (number, default 10000) — Maximum particle buffer size
|
|
233
|
+
- `particleCount` (number, default 100) — Initial active particle count
|
|
234
|
+
|
|
235
|
+
**Key Methods:**
|
|
236
|
+
|
|
237
|
+
- `async initComputePipeline(device: GPUDevice)` — Initialize GPU compute and render pipelines. Must be called before rendering.
|
|
238
|
+
|
|
239
|
+
- `async setTexture(imageBitmap: ImageBitmap)` — Set particle texture from ImageBitmap.
|
|
240
|
+
|
|
241
|
+
- `async setGLBModel(arrayBuffer: ArrayBuffer)` — Use GLB model geometry as particle shape. Automatically extracts textures.
|
|
242
|
+
|
|
243
|
+
- `updateParticles(deltaTime: number)` — Execute physics simulation step on GPU.
|
|
244
|
+
|
|
245
|
+
- `spawnParticles()` — Emit new particles according to emitter configuration.
|
|
246
|
+
|
|
247
|
+
- `setGravity(value: number)` — Set gravity strength (default 9.8).
|
|
248
|
+
|
|
249
|
+
- `setAttractor(strength: number, position: [number, number, number])` — Set attractor point with strength and 3D position.
|
|
250
|
+
|
|
251
|
+
- `render(renderPass: GPURenderPassEncoder)` — Render particles to the current render pass.
|
|
252
|
+
|
|
253
|
+
---
|
|
254
|
+
|
|
255
|
+
### `ParticleSystemManager`
|
|
256
|
+
|
|
257
|
+
Manages multiple particle systems within a single scene.
|
|
258
|
+
|
|
259
|
+
```typescript
|
|
260
|
+
class ParticleSystemManager {
|
|
261
|
+
constructor(device: GPUDevice)
|
|
262
|
+
}
|
|
263
|
+
```
|
|
264
|
+
|
|
265
|
+
**Key Methods:**
|
|
266
|
+
|
|
267
|
+
- `createParticleSystem(config?: object): number` — Create new particle system. Returns system ID.
|
|
268
|
+
|
|
269
|
+
- `getActiveSystem(): ParticleSystem | null` — Get currently active particle system instance.
|
|
270
|
+
|
|
271
|
+
- `getActiveConfig(): object | null` — Get active system configuration.
|
|
272
|
+
|
|
273
|
+
- `setActiveSystem(index: number): boolean` — Switch active system by index. Returns success status.
|
|
274
|
+
|
|
275
|
+
- `removeSystem(index: number): boolean` — Remove system by index. Returns success status.
|
|
276
|
+
|
|
277
|
+
- `updateAllSystems(deltaTime: number)` — Update physics for all systems.
|
|
278
|
+
|
|
279
|
+
- `getSystemsList(): Array<{name: string, id: number, index: number, isActive: boolean}>` — Get list of all systems with metadata.
|
|
280
|
+
|
|
281
|
+
- `duplicateActiveSystem(): number` — Clone active system. Returns new system ID.
|
|
282
|
+
|
|
283
|
+
- `async replaceSystems(sceneData: object): boolean` — Load scene from serialized data. Returns success status.
|
|
284
|
+
|
|
285
|
+
---
|
|
286
|
+
|
|
287
|
+
### `parseGLB(arrayBuffer)`
|
|
288
|
+
|
|
289
|
+
Parse GLB binary format and extract geometry data.
|
|
290
|
+
|
|
291
|
+
```typescript
|
|
292
|
+
async function parseGLB(arrayBuffer: ArrayBuffer): Promise<{
|
|
293
|
+
positions: Float32Array,
|
|
294
|
+
normals: Float32Array,
|
|
295
|
+
indices: Uint16Array | Uint32Array,
|
|
296
|
+
texCoords: Float32Array | null,
|
|
297
|
+
vertexCount: number,
|
|
298
|
+
indexCount: number,
|
|
299
|
+
animationData: object | null,
|
|
300
|
+
hasBaseColorTexture: boolean
|
|
301
|
+
}>
|
|
302
|
+
```
|
|
303
|
+
|
|
304
|
+
**Parameters:**
|
|
305
|
+
- `arrayBuffer` — GLB file as ArrayBuffer
|
|
306
|
+
|
|
307
|
+
**Returns:** Parsed geometry and animation data
|
|
308
|
+
|
|
309
|
+
---
|
|
310
|
+
|
|
311
|
+
### `GLBAnimator`
|
|
312
|
+
|
|
313
|
+
Handles skeletal animation playback for animated GLB models.
|
|
314
|
+
|
|
315
|
+
```typescript
|
|
316
|
+
class GLBAnimator {
|
|
317
|
+
constructor(animationData: object)
|
|
318
|
+
|
|
319
|
+
currentTime: number
|
|
320
|
+
playing: boolean
|
|
321
|
+
speed: number // default 1.0
|
|
322
|
+
loop: boolean // default true
|
|
323
|
+
}
|
|
324
|
+
```
|
|
325
|
+
|
|
326
|
+
**Key Methods:**
|
|
327
|
+
|
|
328
|
+
- `setRestPose(positions: Float32Array, normals: Float32Array)` — Set T-pose/bind pose for animation.
|
|
329
|
+
|
|
330
|
+
- `update(deltaTime: number): { positions: Float32Array, normals: Float32Array, changed: boolean }` — Advance animation by deltaTime. Returns deformed geometry and change status.
|
|
331
|
+
|
|
332
|
+
- `setAnimation(index: number)` — Switch to animation clip by index.
|
|
333
|
+
|
|
334
|
+
- `getAnimationNames(): string[]` — Get list of available animation clip names.
|
|
335
|
+
|
|
336
|
+
---
|
|
337
|
+
|
|
338
|
+
### Secondary Exports
|
|
339
|
+
|
|
340
|
+
The following modules are also exported for advanced usage:
|
|
341
|
+
|
|
342
|
+
- **`ParticleEmitter`** — Emission shape configuration (sphere, cube, cylinder, circle, square)
|
|
343
|
+
- **`ParticlePhysics`** — Physics simulation parameter management
|
|
344
|
+
- **`ParticleTextureManager`** — Texture loading utilities
|
|
345
|
+
- **`Objects3DManager`** — 3D object scene management
|
|
346
|
+
- **`saveScene(manager)`** — Export scene as JSON file download
|
|
347
|
+
- **`loadScene(event)`** — Load scene from file input event
|
|
348
|
+
- **`extractGLBTexture(arrayBuffer)`** — Extract base color texture from GLB file
|
|
349
|
+
- **Shader exports** — WGSL shader code for compute and rendering pipelines
|
|
350
|
+
- **Geometry helpers** — Primitive shape generators (cube, sphere, etc.)
|
|
351
|
+
- **Render pipeline creators** — Low-level WebGPU pipeline construction
|
|
352
|
+
|
|
353
|
+
## GLB Models & Animation
|
|
354
|
+
|
|
355
|
+
Load and animate 3D models:
|
|
356
|
+
|
|
357
|
+
```javascript
|
|
358
|
+
import { parseGLB, GLBAnimator } from 'hz-particles';
|
|
359
|
+
|
|
360
|
+
// Load GLB file
|
|
361
|
+
const response = await fetch('model.glb');
|
|
362
|
+
const arrayBuffer = await response.arrayBuffer();
|
|
363
|
+
const glbData = await parseGLB(arrayBuffer);
|
|
364
|
+
|
|
365
|
+
// Use as particle shape
|
|
366
|
+
await system.setGLBModel(arrayBuffer);
|
|
367
|
+
|
|
368
|
+
// Setup animation (if model has animations)
|
|
369
|
+
if (glbData.animationData) {
|
|
370
|
+
const animator = new GLBAnimator(glbData.animationData);
|
|
371
|
+
animator.setRestPose(glbData.positions, glbData.normals);
|
|
372
|
+
animator.playing = true;
|
|
373
|
+
animator.loop = true;
|
|
374
|
+
animator.speed = 1.0;
|
|
375
|
+
|
|
376
|
+
// In render loop:
|
|
377
|
+
const { positions, normals, changed } = animator.update(deltaTime);
|
|
378
|
+
if (changed) {
|
|
379
|
+
// Update particle system with new geometry
|
|
380
|
+
// (See full API documentation for geometry update methods)
|
|
381
|
+
}
|
|
382
|
+
}
|
|
383
|
+
```
|
|
384
|
+
|
|
385
|
+
## Scene Save/Load
|
|
386
|
+
|
|
387
|
+
Serialize and restore particle configurations:
|
|
388
|
+
|
|
389
|
+
```javascript
|
|
390
|
+
import { saveScene, loadScene } from 'hz-particles';
|
|
391
|
+
|
|
392
|
+
// Save current scene
|
|
393
|
+
const button = document.getElementById('save-button');
|
|
394
|
+
button.addEventListener('click', () => {
|
|
395
|
+
saveScene(manager); // Downloads scene.json
|
|
396
|
+
});
|
|
397
|
+
|
|
398
|
+
// Load scene from file input
|
|
399
|
+
const input = document.getElementById('file-input');
|
|
400
|
+
input.addEventListener('change', async (event) => {
|
|
401
|
+
const success = await loadScene(event, manager);
|
|
402
|
+
if (success) {
|
|
403
|
+
console.log('Scene loaded successfully');
|
|
404
|
+
}
|
|
405
|
+
});
|
|
406
|
+
```
|
|
407
|
+
|
|
408
|
+
## Online Editor
|
|
409
|
+
|
|
410
|
+
Try the interactive particle editor: [Online Editor](https://your-domain.com/editor) (deployment URL coming soon)
|
|
411
|
+
|
|
412
|
+
The editor provides a visual interface for:
|
|
413
|
+
- Real-time particle system configuration
|
|
414
|
+
- Emitter shape selection and tuning
|
|
415
|
+
- GLB model import and animation control
|
|
416
|
+
- Scene preset library
|
|
417
|
+
- Export/import scene JSON
|
|
418
|
+
|
|
419
|
+
## TypeScript
|
|
420
|
+
|
|
421
|
+
Type declarations are not yet included in this package. For TypeScript projects, you can suppress the import error:
|
|
422
|
+
|
|
423
|
+
```typescript
|
|
424
|
+
// @ts-ignore
|
|
425
|
+
import { ParticleSystem, initWebGPU } from 'hz-particles';
|
|
426
|
+
```
|
|
427
|
+
|
|
428
|
+
Community-contributed type definitions are welcome via PR.
|
|
429
|
+
|
|
430
|
+
## Contributing
|
|
431
|
+
|
|
432
|
+
Contributions are welcome! Please follow these steps:
|
|
433
|
+
|
|
434
|
+
1. Fork the repository
|
|
435
|
+
2. Create a feature branch (`git checkout -b feature/amazing-feature`)
|
|
436
|
+
3. Commit your changes (`git commit -m 'Add amazing feature'`)
|
|
437
|
+
4. Push to the branch (`git push origin feature/amazing-feature`)
|
|
438
|
+
5. Open a Pull Request
|
|
439
|
+
|
|
440
|
+
## License
|
|
441
|
+
|
|
442
|
+
MIT License - see [LICENSE](LICENSE) file for details.
|
|
443
|
+
|
|
444
|
+
Copyright (c) 2025 HZ
|