hz-particles 1.0.14 → 1.2.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 +84 -60
- package/dist-lib/hz-particles-r3f.cjs +3 -3
- package/dist-lib/hz-particles-r3f.d.ts +22 -17
- package/dist-lib/hz-particles-r3f.mjs +206 -403
- package/dist-lib/hz-particles.cjs +126 -14
- package/dist-lib/hz-particles.d.ts +178 -10
- package/dist-lib/hz-particles.mjs +2387 -1099
- package/package.json +4 -3
|
@@ -12,7 +12,8 @@ export interface ParticleSystemConfig {
|
|
|
12
12
|
lifetime?: number;
|
|
13
13
|
emissionRate?: number;
|
|
14
14
|
emissionDuration?: number;
|
|
15
|
-
|
|
15
|
+
emissionDurationInfinite?: boolean;
|
|
16
|
+
emissionShape?: 'cube' | 'sphere' | 'square' | 'circle' | 'cylinder' | 'plain' | 'cone' | 'torus' | 'line' | 'hemisphere' | 'disc' | 'annulus' | 'capsule' | 'arc' | 'spiral' | 'frustum' | 'cubeSurface' | 'sphereSurface' | 'boxFrame' | 'polygon' | 'rectangle' | 'point';
|
|
16
17
|
particleSize?: number;
|
|
17
18
|
particleSpeed?: number;
|
|
18
19
|
particleColor?: [number, number, number];
|
|
@@ -24,6 +25,7 @@ export interface ParticleSystemConfig {
|
|
|
24
25
|
randomColors?: number[][];
|
|
25
26
|
textureEnabled?: boolean;
|
|
26
27
|
textureType?: 'image' | 'glb';
|
|
28
|
+
textureImageData?: string;
|
|
27
29
|
glbModelEnabled?: boolean;
|
|
28
30
|
glbFileName?: string | null;
|
|
29
31
|
glbAnimated?: boolean;
|
|
@@ -33,7 +35,9 @@ export interface ParticleSystemConfig {
|
|
|
33
35
|
useGlbTexture?: boolean;
|
|
34
36
|
glbHasTexture?: boolean;
|
|
35
37
|
particleShape?: 'square' | 'circle' | 'triangle' | 'diamond' | 'star' | 'hexagon' | 'ring' | 'heart' | 'cross' | 'spark' | 'leaf' | 'capsule' | 'crescent' | 'line' | 'curved-line';
|
|
36
|
-
|
|
38
|
+
particleShapeRotationX?: number;
|
|
39
|
+
particleShapeRotationY?: number;
|
|
40
|
+
particleShapeRotationZ?: number;
|
|
37
41
|
pulseEnabled?: boolean;
|
|
38
42
|
pulseAmplitude?: number;
|
|
39
43
|
pulseFrequency?: number;
|
|
@@ -47,19 +51,18 @@ export interface ParticleSystemConfig {
|
|
|
47
51
|
attractorEnabled?: boolean;
|
|
48
52
|
attractorStrength?: number;
|
|
49
53
|
attractorPosition?: [number, number, number];
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
54
|
+
emissionRotationX?: number;
|
|
55
|
+
emissionRotationY?: number;
|
|
56
|
+
emissionRotationZ?: number;
|
|
57
|
+
emissionPositionX?: number;
|
|
58
|
+
emissionPositionY?: number;
|
|
59
|
+
emissionPositionZ?: number;
|
|
56
60
|
overrideXVelocity?: boolean;
|
|
57
61
|
overrideYVelocity?: boolean;
|
|
58
62
|
overrideZVelocity?: boolean;
|
|
59
63
|
xVelocity?: number;
|
|
60
64
|
yVelocity?: number;
|
|
61
65
|
zVelocity?: number;
|
|
62
|
-
cubeLength?: number;
|
|
63
66
|
outerLength?: number;
|
|
64
67
|
innerLength?: number;
|
|
65
68
|
outerRadius?: number;
|
|
@@ -68,6 +71,8 @@ export interface ParticleSystemConfig {
|
|
|
68
71
|
squareInnerSize?: number;
|
|
69
72
|
circleOuterRadius?: number;
|
|
70
73
|
circleInnerRadius?: number;
|
|
74
|
+
rectangleWidth?: number;
|
|
75
|
+
rectangleHeight?: number;
|
|
71
76
|
circleVelocityDirection?: string;
|
|
72
77
|
cylinderOuterRadius?: number;
|
|
73
78
|
cylinderInnerRadius?: number;
|
|
@@ -81,6 +86,12 @@ export interface ParticleSystemConfig {
|
|
|
81
86
|
aspectRatio?: number;
|
|
82
87
|
velocityStretchEnabled?: boolean;
|
|
83
88
|
velocityStretchFactor?: number;
|
|
89
|
+
blendMode?: 'normal' | 'additive';
|
|
90
|
+
noiseDistortEnabled?: boolean;
|
|
91
|
+
noiseTilingX?: number;
|
|
92
|
+
noiseTilingY?: number;
|
|
93
|
+
noiseSpeed?: number;
|
|
94
|
+
noiseAmplitude?: number;
|
|
84
95
|
randomSize?: boolean;
|
|
85
96
|
minSize?: number;
|
|
86
97
|
maxSize?: number;
|
|
@@ -97,6 +108,7 @@ export interface ParticleSystemConfig {
|
|
|
97
108
|
emissionTrailMinDistance?: number;
|
|
98
109
|
emissionTrailMaxPoints?: number;
|
|
99
110
|
emissionTrailSegments?: number;
|
|
111
|
+
emissionTrailMode?: 'ribbon' | 'particles';
|
|
100
112
|
emissionTrailShape?: 'straight' | 'zigzag' | 'sine' | 'spiral';
|
|
101
113
|
emissionTrailShapeAmplitude?: number;
|
|
102
114
|
emissionTrailShapeFrequency?: number;
|
|
@@ -106,7 +118,9 @@ export interface ParticleSystemConfig {
|
|
|
106
118
|
followSystemId?: number | null;
|
|
107
119
|
hidden?: boolean;
|
|
108
120
|
oneOnlyMode?: boolean;
|
|
121
|
+
script?: string;
|
|
109
122
|
glbModelData?: string;
|
|
123
|
+
displayUnit?: 'm' | 'cm';
|
|
110
124
|
onAppearanceChange?: ((config: ParticleSystemConfig) => void) | null;
|
|
111
125
|
onColorChange?: ((config: ParticleSystemConfig) => void) | null;
|
|
112
126
|
onSizeChange?: ((config: ParticleSystemConfig) => void) | null;
|
|
@@ -186,6 +200,8 @@ export interface BindGroupLayouts {
|
|
|
186
200
|
|
|
187
201
|
export interface RenderPipelines {
|
|
188
202
|
particlePipeline: GPURenderPipeline;
|
|
203
|
+
particleAdditivePipeline: GPURenderPipeline;
|
|
204
|
+
particleDepthWritePipeline: GPURenderPipeline;
|
|
189
205
|
blurPipeline: GPURenderPipeline;
|
|
190
206
|
compositePipeline: GPURenderPipeline;
|
|
191
207
|
directRenderPipeline: GPURenderPipeline;
|
|
@@ -243,6 +259,8 @@ export class ParticleSystem {
|
|
|
243
259
|
activeParticles: number;
|
|
244
260
|
emitting: boolean;
|
|
245
261
|
currentEmissionTime: number;
|
|
262
|
+
/** true after destroy() — GPU buffers are gone, all mutating methods become no-ops. */
|
|
263
|
+
destroyed: boolean;
|
|
246
264
|
particleData: Float32Array;
|
|
247
265
|
particleVelocities: Float32Array;
|
|
248
266
|
instanceBuffer: GPUBuffer;
|
|
@@ -290,6 +308,7 @@ export class ParticleSystem {
|
|
|
290
308
|
outParticleData: Float32Array,
|
|
291
309
|
outVelocities: Float32Array
|
|
292
310
|
): Promise<PhysicsReadbackResult>;
|
|
311
|
+
setScript(source: string | null): void;
|
|
293
312
|
destroy(): void;
|
|
294
313
|
respawnParticle(oldIndex: number, newIndex: number): void;
|
|
295
314
|
updateBuffers(): void;
|
|
@@ -307,6 +326,9 @@ export class ParticleSystemManager {
|
|
|
307
326
|
activeSystemIndex: number;
|
|
308
327
|
systemCounter: number;
|
|
309
328
|
onSystemCreated: ((systemId: number, config: ParticleSystemConfig) => void) | null;
|
|
329
|
+
/** false while replaceSystems()/addSystems() rebuild GPU resources — hosts
|
|
330
|
+
* driving their own render loop must skip frames while !ready. */
|
|
331
|
+
ready: boolean;
|
|
310
332
|
|
|
311
333
|
createParticleSystem(config?: ParticleSystemConfig): number;
|
|
312
334
|
getSystemById(id: number): SystemEntry | null;
|
|
@@ -320,7 +342,8 @@ export class ParticleSystemManager {
|
|
|
320
342
|
getSystemsList(): SystemInfo[];
|
|
321
343
|
duplicateActiveSystem(): number;
|
|
322
344
|
replaceSystems(sceneData: SceneData): Promise<boolean>;
|
|
323
|
-
|
|
345
|
+
/** Returns the ids of the systems it created (race-proof handle), or false on error. */
|
|
346
|
+
addSystems(sceneData: SceneData, positionOffset?: [number, number, number]): Promise<number[] | false>;
|
|
324
347
|
}
|
|
325
348
|
|
|
326
349
|
export class ParticleEmitter {
|
|
@@ -387,6 +410,13 @@ export class ParticlePhysics {
|
|
|
387
410
|
instanceBuffer: GPUBuffer,
|
|
388
411
|
velocityBuffer: GPUBuffer
|
|
389
412
|
): Promise<PhysicsReadbackResult>;
|
|
413
|
+
readbackForScript(
|
|
414
|
+
activeParticles: number,
|
|
415
|
+
outParticleData: Float32Array,
|
|
416
|
+
outVelocities: Float32Array,
|
|
417
|
+
instanceBuffer: GPUBuffer,
|
|
418
|
+
velocityBuffer: GPUBuffer
|
|
419
|
+
): Promise<PhysicsReadbackResult>;
|
|
390
420
|
destroy(): void;
|
|
391
421
|
setDamping(dampingValue: number): void;
|
|
392
422
|
setGravity(gravityValue: number): void;
|
|
@@ -483,6 +513,23 @@ export class Objects3DManager {
|
|
|
483
513
|
): void;
|
|
484
514
|
}
|
|
485
515
|
|
|
516
|
+
export class ParticleScript {
|
|
517
|
+
constructor(source: string, config: ParticleSystemConfig, maxParticles: number);
|
|
518
|
+
compile(source: string): void;
|
|
519
|
+
execute(
|
|
520
|
+
deltaTime: number,
|
|
521
|
+
time: number,
|
|
522
|
+
frame: number,
|
|
523
|
+
totalFrames: number,
|
|
524
|
+
particleData: Float32Array,
|
|
525
|
+
velocityData: Float32Array,
|
|
526
|
+
count: number
|
|
527
|
+
): boolean;
|
|
528
|
+
snapshotConfig(config: ParticleSystemConfig): void;
|
|
529
|
+
restoreConfig(config: ParticleSystemConfig): void;
|
|
530
|
+
destroy(): void;
|
|
531
|
+
}
|
|
532
|
+
|
|
486
533
|
// ─── WebGPU Utilities ────────────────────────────────────────────────────────
|
|
487
534
|
|
|
488
535
|
export function initWebGPU(canvas: HTMLCanvasElement): Promise<WebGPUContext>;
|
|
@@ -572,6 +619,127 @@ export function saveScene(particleSystemManager: ParticleSystemManager): void;
|
|
|
572
619
|
|
|
573
620
|
export function loadScene(event: Event): Promise<SceneData>;
|
|
574
621
|
|
|
622
|
+
/** THE single source of truth for the serializable (non-binary) fields of a system config.
|
|
623
|
+
* saveScene, the exported standalone code, and editor undo snapshots all build their
|
|
624
|
+
* per-system object from this so the field set can never drift. Binary assets
|
|
625
|
+
* (textureImageData / glbModelData) are added by the caller. */
|
|
626
|
+
export function serializeSystemConfig(config: ParticleSystemConfig): Record<string, unknown>;
|
|
627
|
+
|
|
575
628
|
// ─── Preset Fetching ─────────────────────────────────────────────────────────
|
|
576
629
|
|
|
577
630
|
export function fetchPreset(url: string): Promise<SceneData>;
|
|
631
|
+
|
|
632
|
+
// ─── .hzfx — self-contained binary effect package ────────────────────────────
|
|
633
|
+
|
|
634
|
+
export interface PackHZFXOptions {
|
|
635
|
+
/** Transcode embedded textures to WebP (browser only — needs OffscreenCanvas). */
|
|
636
|
+
textureFormat?: 'keep' | 'webp';
|
|
637
|
+
/** WebP quality 0..1 (default 0.85). */
|
|
638
|
+
quality?: number;
|
|
639
|
+
}
|
|
640
|
+
|
|
641
|
+
/** Pack a SceneData into a self-contained .hzfx binary package (config + assets). */
|
|
642
|
+
export function packHZFX(sceneData: SceneData, opts?: PackHZFXOptions): Promise<ArrayBuffer>;
|
|
643
|
+
|
|
644
|
+
/** Unpack a .hzfx package back into a SceneData the engine consumes. */
|
|
645
|
+
export function unpackHZFX(arrayBuffer: ArrayBuffer): SceneData;
|
|
646
|
+
|
|
647
|
+
/** True if the buffer starts with the HZFX magic. */
|
|
648
|
+
export function isHZFX(arrayBuffer: ArrayBuffer): boolean;
|
|
649
|
+
|
|
650
|
+
// ─── Engine-faithful FX overlay/inline renderer ──────────────────────────────
|
|
651
|
+
|
|
652
|
+
export interface HzFxEmitter {
|
|
653
|
+
preset: SceneData;
|
|
654
|
+
position?: [number, number, number];
|
|
655
|
+
}
|
|
656
|
+
|
|
657
|
+
export interface HzFxOverlayOptions {
|
|
658
|
+
/** Inline mode only — return the host scene's depth texture (or a
|
|
659
|
+
* depth-only view) each frame to occlude particles behind host geometry. */
|
|
660
|
+
getSceneDepth?: () => GPUTexture | { view: GPUTextureView; multisampled?: boolean } | null;
|
|
661
|
+
/** Respawn all systems when everything is dead (preview-style looping). Default true. */
|
|
662
|
+
autoRespawn?: boolean;
|
|
663
|
+
/** When true (default), the effect's saved group `loop` flag overrides autoRespawn.
|
|
664
|
+
* Hosts owning their own respawn timeline (the editor) pass false. */
|
|
665
|
+
respectEffectLoop?: boolean;
|
|
666
|
+
/** Share an existing ParticleSystemManager instead of creating one — lets a host
|
|
667
|
+
* (e.g. the hz-editor) keep all its UI/config wiring while the overlay is the single
|
|
668
|
+
* render path. The overlay then owns updateAllSystems/GLB-update/trail/draw each render(). */
|
|
669
|
+
manager?: ParticleSystemManager;
|
|
670
|
+
}
|
|
671
|
+
|
|
672
|
+
export interface HzFxOverlay {
|
|
673
|
+
manager: ParticleSystemManager;
|
|
674
|
+
device: GPUDevice;
|
|
675
|
+
context: GPUCanvasContext;
|
|
676
|
+
/** Column-major 4×4 matrices (e.g. three camera.projectionMatrix.elements /
|
|
677
|
+
* camera.matrixWorldInverse.elements) + camera world position. */
|
|
678
|
+
setCamera(
|
|
679
|
+
projectionMatrix: Float32Array | number[],
|
|
680
|
+
viewMatrix: Float32Array | number[],
|
|
681
|
+
position?: [number, number, number] | { x: number; y: number; z: number },
|
|
682
|
+
): void;
|
|
683
|
+
setCameraMVP(
|
|
684
|
+
mvp: Float32Array | number[],
|
|
685
|
+
position?: [number, number, number] | { x: number; y: number; z: number },
|
|
686
|
+
): void;
|
|
687
|
+
setEmitters(emitters: HzFxEmitter[]): Promise<void>;
|
|
688
|
+
loadPreset(preset: SceneData, position?: [number, number, number]): Promise<void>;
|
|
689
|
+
/** Add a preset as a STATIC group at `position` (e.g. a map FX, a splash). Returns a
|
|
690
|
+
* handle to remove exactly these systems. Any number coexist in the same overlay —
|
|
691
|
+
* prefer this over reaching into `manager.addSystems` directly. */
|
|
692
|
+
addEmitter(preset: SceneData, position?: [number, number, number]): Promise<{ remove(): void }>;
|
|
693
|
+
/** Add a preset as a MOVING emitter (e.g. a ball trail); the trail echoes follow
|
|
694
|
+
* the recorded path. Two ways to drive its position:
|
|
695
|
+
* - PULL (recommended): pass `{ getPosition }` — the overlay reads it during its
|
|
696
|
+
* own render(), once/frame at the right time, so the host never pushes per-frame
|
|
697
|
+
* (no timing race vs the host loop). Return null to pause the emitter.
|
|
698
|
+
* - PUSH: call the returned setPosition() each frame (for simple hosts). */
|
|
699
|
+
addMovingEmitter(
|
|
700
|
+
preset: SceneData,
|
|
701
|
+
opts?: { getPosition?: () => [number, number, number] | { x: number; y: number; z: number } | null },
|
|
702
|
+
): Promise<{
|
|
703
|
+
setPosition(p: [number, number, number] | { x: number; y: number; z: number }): void;
|
|
704
|
+
remove(): void;
|
|
705
|
+
}>;
|
|
706
|
+
/** Simulate + render one frame (deltaTime in seconds). Inline mode: call AFTER the host scene render. */
|
|
707
|
+
render(deltaTime: number): void;
|
|
708
|
+
resize(): void;
|
|
709
|
+
/** Drop cached per-system bind groups. Call after a replaceSystems/addSystems that may
|
|
710
|
+
* reuse a system id with a new system object (stale appearance/texture otherwise). */
|
|
711
|
+
clearCaches(): void;
|
|
712
|
+
/** Track existing manager systems (by id) as a path-history group so their trails use the
|
|
713
|
+
* same curved `hasPathHistory` path as moving emitters — history ONLY (the host keeps
|
|
714
|
+
* owning position/rotation/visibility/respawn). Used by the editor's simulate-move. */
|
|
715
|
+
trackHistoryGroup(
|
|
716
|
+
ids: Iterable<number>,
|
|
717
|
+
getPosition: () => [number, number, number] | { x: number; y: number; z: number } | null,
|
|
718
|
+
): { remove(): void };
|
|
719
|
+
destroy(): void;
|
|
720
|
+
}
|
|
721
|
+
|
|
722
|
+
/**
|
|
723
|
+
* Engine-faithful FX rendering for host applications — the same render loop as
|
|
724
|
+
* the HZ Particles previews (library pipelines: shader shapes, per-system
|
|
725
|
+
* blending, GLB meshes, multi-pass bloom), driven by the host camera.
|
|
726
|
+
*
|
|
727
|
+
* Overlay mode: pass a canvas (own transparent WebGPU context, stack it above
|
|
728
|
+
* your scene). Inline mode: pass { device, context, canvas } from your own
|
|
729
|
+
* WebGPU renderer and call render() after your scene render each frame.
|
|
730
|
+
*/
|
|
731
|
+
export function initHzFxOverlay(
|
|
732
|
+
target: HTMLCanvasElement | { device: GPUDevice; context: GPUCanvasContext; canvas?: HTMLCanvasElement },
|
|
733
|
+
options?: HzFxOverlayOptions,
|
|
734
|
+
): Promise<HzFxOverlay>;
|
|
735
|
+
|
|
736
|
+
/**
|
|
737
|
+
* Build a `getSceneDepth` callback (for initHzFxOverlay inline mode) from a three.js
|
|
738
|
+
* WebGPURenderer, so the overlay occludes particles behind host geometry. Duck-typed —
|
|
739
|
+
* pass the renderer; no three import. Handles tone-mapping/sRGB internal framebuffers and
|
|
740
|
+
* returns null (no occlusion, FX stay visible) when the scene depth isn't reachable
|
|
741
|
+
* (e.g. a PostProcessing pass owns the output). Pass the result as `options.getSceneDepth`.
|
|
742
|
+
*/
|
|
743
|
+
export function makeThreeSceneDepth(
|
|
744
|
+
gl: unknown,
|
|
745
|
+
): () => (GPUTexture | { view: GPUTextureView; multisampled?: boolean } | null);
|