hz-particles 1.0.15 → 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.
@@ -12,6 +12,7 @@ export interface ParticleSystemConfig {
12
12
  lifetime?: number;
13
13
  emissionRate?: number;
14
14
  emissionDuration?: number;
15
+ emissionDurationInfinite?: boolean;
15
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;
@@ -34,7 +35,9 @@ export interface ParticleSystemConfig {
34
35
  useGlbTexture?: boolean;
35
36
  glbHasTexture?: boolean;
36
37
  particleShape?: 'square' | 'circle' | 'triangle' | 'diamond' | 'star' | 'hexagon' | 'ring' | 'heart' | 'cross' | 'spark' | 'leaf' | 'capsule' | 'crescent' | 'line' | 'curved-line';
37
- particleShapeRotation?: number;
38
+ particleShapeRotationX?: number;
39
+ particleShapeRotationY?: number;
40
+ particleShapeRotationZ?: number;
38
41
  pulseEnabled?: boolean;
39
42
  pulseAmplitude?: number;
40
43
  pulseFrequency?: number;
@@ -51,16 +54,15 @@ export interface ParticleSystemConfig {
51
54
  emissionRotationX?: number;
52
55
  emissionRotationY?: number;
53
56
  emissionRotationZ?: number;
54
- emissionTranslationX?: number;
55
- emissionTranslationY?: number;
56
- emissionTranslationZ?: number;
57
+ emissionPositionX?: number;
58
+ emissionPositionY?: number;
59
+ emissionPositionZ?: number;
57
60
  overrideXVelocity?: boolean;
58
61
  overrideYVelocity?: boolean;
59
62
  overrideZVelocity?: boolean;
60
63
  xVelocity?: number;
61
64
  yVelocity?: number;
62
65
  zVelocity?: number;
63
- cubeLength?: number;
64
66
  outerLength?: number;
65
67
  innerLength?: number;
66
68
  outerRadius?: number;
@@ -84,6 +86,12 @@ export interface ParticleSystemConfig {
84
86
  aspectRatio?: number;
85
87
  velocityStretchEnabled?: boolean;
86
88
  velocityStretchFactor?: number;
89
+ blendMode?: 'normal' | 'additive';
90
+ noiseDistortEnabled?: boolean;
91
+ noiseTilingX?: number;
92
+ noiseTilingY?: number;
93
+ noiseSpeed?: number;
94
+ noiseAmplitude?: number;
87
95
  randomSize?: boolean;
88
96
  minSize?: number;
89
97
  maxSize?: number;
@@ -100,6 +108,7 @@ export interface ParticleSystemConfig {
100
108
  emissionTrailMinDistance?: number;
101
109
  emissionTrailMaxPoints?: number;
102
110
  emissionTrailSegments?: number;
111
+ emissionTrailMode?: 'ribbon' | 'particles';
103
112
  emissionTrailShape?: 'straight' | 'zigzag' | 'sine' | 'spiral';
104
113
  emissionTrailShapeAmplitude?: number;
105
114
  emissionTrailShapeFrequency?: number;
@@ -111,6 +120,7 @@ export interface ParticleSystemConfig {
111
120
  oneOnlyMode?: boolean;
112
121
  script?: string;
113
122
  glbModelData?: string;
123
+ displayUnit?: 'm' | 'cm';
114
124
  onAppearanceChange?: ((config: ParticleSystemConfig) => void) | null;
115
125
  onColorChange?: ((config: ParticleSystemConfig) => void) | null;
116
126
  onSizeChange?: ((config: ParticleSystemConfig) => void) | null;
@@ -190,6 +200,8 @@ export interface BindGroupLayouts {
190
200
 
191
201
  export interface RenderPipelines {
192
202
  particlePipeline: GPURenderPipeline;
203
+ particleAdditivePipeline: GPURenderPipeline;
204
+ particleDepthWritePipeline: GPURenderPipeline;
193
205
  blurPipeline: GPURenderPipeline;
194
206
  compositePipeline: GPURenderPipeline;
195
207
  directRenderPipeline: GPURenderPipeline;
@@ -247,6 +259,8 @@ export class ParticleSystem {
247
259
  activeParticles: number;
248
260
  emitting: boolean;
249
261
  currentEmissionTime: number;
262
+ /** true after destroy() — GPU buffers are gone, all mutating methods become no-ops. */
263
+ destroyed: boolean;
250
264
  particleData: Float32Array;
251
265
  particleVelocities: Float32Array;
252
266
  instanceBuffer: GPUBuffer;
@@ -312,6 +326,9 @@ export class ParticleSystemManager {
312
326
  activeSystemIndex: number;
313
327
  systemCounter: number;
314
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;
315
332
 
316
333
  createParticleSystem(config?: ParticleSystemConfig): number;
317
334
  getSystemById(id: number): SystemEntry | null;
@@ -325,7 +342,8 @@ export class ParticleSystemManager {
325
342
  getSystemsList(): SystemInfo[];
326
343
  duplicateActiveSystem(): number;
327
344
  replaceSystems(sceneData: SceneData): Promise<boolean>;
328
- addSystems(sceneData: SceneData, positionOffset?: [number, number, number]): Promise<boolean>;
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>;
329
347
  }
330
348
 
331
349
  export class ParticleEmitter {
@@ -601,6 +619,127 @@ export function saveScene(particleSystemManager: ParticleSystemManager): void;
601
619
 
602
620
  export function loadScene(event: Event): Promise<SceneData>;
603
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
+
604
628
  // ─── Preset Fetching ─────────────────────────────────────────────────────────
605
629
 
606
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);