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 CHANGED
@@ -15,7 +15,7 @@ A high-performance WebGPU particle engine for the web. Use it as a standalone We
15
15
  - **Scene Serialization** — Save/load particle configurations as JSON
16
16
  - **Multiple Emitter Shapes** — Sphere, cube, cylinder, circle, square emission patterns
17
17
  - **Real-time Physics** — Gravity, attractors, drag, velocity, and lifetime management
18
- - **React Three Fiber Component** — Drop-in `HZParticlesFX` component with preset support
18
+ - **React Three Fiber Component** — `HZFaithfulFX` (engine-faithful: the real overlay inline on three's WebGPURenderer), with preset support
19
19
  - **3D Object Support** — Static 3D objects alongside particle systems
20
20
 
21
21
  ## Requirements
@@ -46,7 +46,7 @@ npm install hz-particles
46
46
  ```
47
47
 
48
48
  ```javascript
49
- import { HZParticlesFX } from 'hz-particles/r3f';
49
+ import { HZFaithfulFX } from 'hz-particles/r3f';
50
50
  ```
51
51
 
52
52
  > **Important:** Your R3F `Canvas` must use `WebGPURenderer`. Pass `gl={{ powerPreference: 'high-performance' }}` and configure Three.js to use its WebGPU backend, or use `@react-three/fiber` with a WebGPU-capable renderer setup.
@@ -109,70 +109,45 @@ render();
109
109
 
110
110
  ### React Three Fiber
111
111
 
112
- **Minimal example with a preset file:**
112
+ `<HZFaithfulFX>` runs the **real engine** (the overlay) inline on three's `WebGPURenderer`: shader
113
+ shapes, noise, per-system blending, multi-pass bloom, faithful trails + depth occlusion. What you
114
+ design in the editor is exactly what renders. Pass a `position` for a static FX, or a `positionRef`
115
+ for a moving one (a ball trail) — the overlay pulls the ref each frame.
113
116
 
114
117
  ```jsx
115
- import { HZParticlesFX } from 'hz-particles/r3f';
116
- import explosionPreset from './presets/explosion.json';
118
+ import { useRef } from 'react';
119
+ import { HZFaithfulFX } from 'hz-particles/r3f';
120
+ import firePreset from './presets/fire.json';
117
121
 
118
122
  function Scene() {
119
- return (
120
- <HZParticlesFX
121
- preset={explosionPreset}
122
- position={[0, 1, 0]}
123
- />
124
- );
125
- }
126
- ```
127
-
128
- **Complete example with all key props:**
129
-
130
- ```jsx
131
- import { useRef, useState } from 'react';
132
- import { HZParticlesFX } from 'hz-particles/r3f';
133
- import explosionPreset from './presets/explosion.json';
134
-
135
- function Scene() {
136
- const targetRef = useRef();
137
- const [resetKey, setResetKey] = useState(0);
138
-
123
+ const ballRef = useRef(null); // THREE.Vector3, updated by your app each frame
139
124
  return (
140
125
  <>
141
- <mesh ref={targetRef} position={[0, 1, 0]}>
142
- <boxGeometry />
143
- <meshStandardMaterial />
144
- </mesh>
145
-
146
- <HZParticlesFX
147
- preset={explosionPreset}
148
- positionRef={targetRef}
149
- scale={1.5}
150
- resetKey={resetKey}
151
- onComplete={() => console.log('effect finished')}
152
- />
153
-
154
- <button onClick={() => setResetKey(k => k + 1)}>
155
- Replay
156
- </button>
126
+ <HZFaithfulFX preset={firePreset} position={[0, 1, 0]} /> {/* static FX */}
127
+ <HZFaithfulFX preset={trailPreset} positionRef={ballRef} scale={1.5} /> {/* ball trail */}
157
128
  </>
158
129
  );
159
130
  }
160
131
  ```
161
132
 
133
+ `<HZFaithfulFX>` **drives rendering** (its `useFrame` calls `gl.render` itself, so R3F stops
134
+ auto-rendering). If your host owns its own render loop (custom RAF, post-processing pipeline), use
135
+ `initHzFxOverlay()` + `makeThreeSceneDepth()` directly (see above). Re-trigger an effect by remounting
136
+ it (change its `key`).
137
+
162
138
  ## Props Reference
163
139
 
164
- The `HZParticlesFX` component accepts the following props:
140
+ The `HZFaithfulFX` component accepts the following props:
165
141
 
166
142
  | Prop | Type | Default | Description |
167
143
  |------|------|---------|-------------|
168
- | `preset` | `SceneData \| string` | — | Particle preset — pass a JSON object or a URL/path to a JSON file |
169
- | `position` | `[number, number, number]` | `[0, 0, 0]` | Static world-space position of the effect |
170
- | `positionRef` | `React.RefObject<Object3D>` | — | Attach the effect to a scene object; position tracks the ref each frame |
171
- | `autoPlay` | `boolean` | `true` | Start playing immediately when mounted |
172
- | `visible` | `boolean` | `true` | Show or hide the effect without unmounting |
173
- | `scale` | `number` | `1` | Uniform scale applied to the entire effect |
174
- | `resetKey` | `number` | `0` | Increment to reset and respawn the particle system |
175
- | `onComplete` | `() => void` | — | Callback fired when the effect finishes playing |
144
+ | `preset` | `SceneData` | — | Particle preset (JSON exported from the editor) |
145
+ | `position` | `[number, number, number]` | `[0, 0, 0]` | Static emitter world position (used when `positionRef` is omitted) |
146
+ | `positionRef` | `React.RefObject<Vector3 \| null>` | — | Moving emitter (e.g. a ball trail): the overlay reads this ref every frame |
147
+ | `scale` | `number` | `1` | Uniform scale applied to the preset (sizes, speeds, emission, trail) |
148
+ | `active` | `boolean` | `true` | When false, a moving emitter pauses (no new trail) |
149
+ | `renderPriority` | `number` | `1` | `useFrame` priority. This component drives rendering, so keep it > 0 |
150
+ | `noOcclusion` | `boolean` | `false` | Skip scene-depth occlusion (particles never hidden behind geometry) |
176
151
 
177
152
  ## Preset Configuration
178
153
 
@@ -347,6 +322,54 @@ if (glbData.animationData) {
347
322
  }
348
323
  ```
349
324
 
325
+ ## Engine-faithful Integration (overlay renderer)
326
+
327
+ `initHzFxOverlay()` is **the same render loop the HZ previews and editor use** — shader-drawn
328
+ shapes, per-system blending, noise distortion, GLB mesh particles + animation, multi-pass bloom,
329
+ and faithful trails. It's the recommended way to drop HZ effects into any app (vanilla JS,
330
+ TypeScript, or React — one core, no per-framework reimplementation).
331
+
332
+ ```javascript
333
+ import { initHzFxOverlay, makeThreeSceneDepth } from 'hz-particles';
334
+
335
+ // OVERLAY mode: pass a canvas — the overlay owns its WebGPU context and renders on a
336
+ // transparent background. Stack it above your scene with pointer-events: none.
337
+ const fx = await initHzFxOverlay(canvas);
338
+ await fx.loadPreset(preset); // or fx.setEmitters([{ preset, position }])
339
+
340
+ function frame(dt) {
341
+ fx.setCamera(proj, view, [cx, cy, cz]); // column-major matrices + camera world pos
342
+ fx.render(dt);
343
+ }
344
+
345
+ // INLINE mode (share your own WebGPU renderer, e.g. three's WebGPURenderer):
346
+ const fx2 = await initHzFxOverlay(
347
+ { device, context, canvas },
348
+ { getSceneDepth: makeThreeSceneDepth(renderer) } // one-line occlusion behind your geometry
349
+ );
350
+ // each frame, AFTER renderer.render(scene, camera): fx2.setCamera(...); fx2.render(dt);
351
+ ```
352
+
353
+ **Coexisting groups & moving emitters** — one overlay hosts many FX at once. `addEmitter(preset, pos)`
354
+ adds a static group; `addMovingEmitter(preset, { getPosition })` adds a moving one (e.g. a ball trail)
355
+ whose comet trail follows the path you supply (the overlay *pulls* `getPosition()` each frame; return
356
+ `null` to pause). Both return `{ remove() }`.
357
+
358
+ | Method | Description |
359
+ | --- | --- |
360
+ | `setCamera(proj, view, pos)` / `setCameraMVP(mvp, pos)` | Drive the camera (column-major). |
361
+ | `loadPreset(preset, pos?)` / `setEmitters([...])` | (Re)build emitters. |
362
+ | `addEmitter(preset, pos?)` → `{ remove }` | Add a STATIC group that coexists with others. |
363
+ | `addMovingEmitter(preset, { getPosition })` → `{ setPosition, remove }` | Add a MOVING group (ball trail). |
364
+ | `render(dt)` | Simulate + render one frame (inline: call after your scene render). |
365
+ | `resize()` | Recreate render textures (also auto-detected from the canvas). |
366
+ | `clearCaches()` | Drop cached bind groups after a `replaceSystems`/`addSystems` that reuses ids. |
367
+ | `trackHistoryGroup(ids, getPosition)` → `{ remove }` | Give existing manager systems a moving trail (history only). |
368
+ | `destroy()` | Release GPU resources. |
369
+
370
+ Options: `{ getSceneDepth, autoRespawn, manager }`. Pass `manager` to **share an existing
371
+ `ParticleSystemManager`** so your app keeps owning it while the overlay is the single render path.
372
+
350
373
  ## Scene Save/Load
351
374
 
352
375
  ```javascript
@@ -354,7 +377,7 @@ import { saveScene, loadScene } from 'hz-particles';
354
377
 
355
378
  // Save current scene
356
379
  document.getElementById('save-button').addEventListener('click', () => {
357
- saveScene(manager); // Downloads scene.json
380
+ saveScene(manager); // Downloads a self-contained .hzfx package (or .hzfx → JSON on Alt-click)
358
381
  });
359
382
 
360
383
  // Load scene from file input
@@ -368,17 +391,15 @@ document.getElementById('file-input').addEventListener('change', async (event) =
368
391
 
369
392
  ## TypeScript
370
393
 
371
- Type declarations are not yet included in this package. For TypeScript projects, suppress the import error with:
394
+ Type declarations **are shipped** the package's `types` entry points at `dist-lib/hz-particles.d.ts`
395
+ (and `dist-lib/hz-particles-r3f.d.ts` for the `hz-particles/r3f` subpath), so imports are fully typed
396
+ out of the box:
372
397
 
373
398
  ```typescript
374
- // @ts-ignore
375
- import { ParticleSystem, initWebGPU } from 'hz-particles';
376
- // @ts-ignore
377
- import { HZParticlesFX } from 'hz-particles/r3f';
399
+ import { initHzFxOverlay, ParticleSystemManager, serializeSystemConfig } from 'hz-particles';
400
+ import { HZFaithfulFX } from 'hz-particles/r3f';
378
401
  ```
379
402
 
380
- Community-contributed type definitions are welcome via PR.
381
-
382
403
  ## Secondary Exports
383
404
 
384
405
  The following modules are also exported for advanced usage:
@@ -387,8 +408,11 @@ The following modules are also exported for advanced usage:
387
408
  - **`ParticlePhysics`** — Physics simulation parameter management
388
409
  - **`ParticleTextureManager`** — Texture loading utilities
389
410
  - **`Objects3DManager`** — 3D object scene management
390
- - **`saveScene(manager)`** — Export scene as JSON file download
391
- - **`loadScene(event)`** — Load scene from file input event
411
+ - **`saveScene(manager)`** — Export scene as a self-contained `.hzfx` package (or JSON) file download
412
+ - **`loadScene(event)`** — Load scene from file input event (`.hzfx` or JSON, auto-detected)
413
+ - **`serializeSystemConfig(config)`** — Single source of truth for a system's serializable config fields (shared by `saveScene` + the editor's code export; add binary assets yourself)
414
+ - **`initHzFxOverlay(target, options?)`** — Engine-faithful overlay/inline renderer (see above)
415
+ - **`packHZFX` / `unpackHZFX` / `isHZFX`** — Build/read the `.hzfx` binary package format
392
416
  - **`extractGLBTexture(arrayBuffer)`** — Extract base color texture from GLB file
393
417
  - **Shader exports** — WGSL shader code for compute and rendering pipelines
394
418
  - **Geometry helpers** — Primitive shape generators (cube, sphere, etc.)
@@ -1,13 +1,13 @@
1
- "use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const _e=require("react/jsx-runtime"),i=require("react"),ke=require("@react-three/fiber"),qt=require("three"),Qe=require("hz-particles");function Ut(s){const p=Object.create(null,{[Symbol.toStringTag]:{value:"Module"}});if(s){for(const d in s)if(d!=="default"){const V=Object.getOwnPropertyDescriptor(s,d);Object.defineProperty(p,d,V.get?V:{enumerable:!0,get:()=>s[d]})}}return p.default=s,Object.freeze(p)}const c=Ut(qt);function Je(s){return Math.abs(Math.sin(s*12.9898)*43758.5453)%1}function Bt(s){const p=Math.sin(s*54321.67)*43758.5453%1;return p<0?p+1:p}function wt(s,p,d,V){if(d<=0)return 0;const S=p/d;let Y=s.particleSize??.5;if(s.randomSize){const Z=s.minSize??.1,N=s.maxSize??.5;Y=Z+(N-Z)*Bt(d)}const $=Math.max(.01,Math.min(10,s.sizeLifetimeSpeed??1));if(s.fadeSizeEnabled&&(Y*=1-Math.pow(S,1/$)),s.increaseSizeEnabled&&(Y*=1+Math.pow(S,1/$)),s.pulseEnabled){const Z=s.pulseAmplitude??.5,N=s.pulseFrequency??1,oe=(s.pulsePhaseRandom??0)*Je(V)*Math.PI*2;Y*=1+Z*Math.sin(p*N*Math.PI*2+oe)}return Math.max(0,Y)}function zt(s,p,d,V){if(d<=0)return 0;let S=s.opacity??1;const Y=p/d;if(s.fadeEnabled&&(S*=Math.max(0,1-Y)),s.pulseEnabled&&s.pulseOpacity){const $=s.pulseAmplitude??.5,Z=s.pulseFrequency??1,se=(s.pulsePhaseRandom??0)*Je(V)*Math.PI*2;S*=Math.max(0,1+$*Math.sin(p*Z*Math.PI*2+se))}return Math.max(0,Math.min(1,S))}function At(s,p){return p===1?s:{...s,systems:s.systems.map(d=>({...d,particleSize:(d.particleSize??.5)*p,minSize:d.minSize!=null?d.minSize*p:void 0,maxSize:d.maxSize!=null?d.maxSize*p:void 0,particleSpeed:(d.particleSpeed??1)*p,minSpeed:d.minSpeed!=null?d.minSpeed*p:void 0,maxSpeed:d.maxSpeed!=null?d.maxSpeed*p:void 0,emissionTrailWidth:(d.emissionTrailWidth??.3)*p,emissionTrailShapeAmplitude:d.emissionTrailShapeAmplitude!=null?d.emissionTrailShapeAmplitude*p:void 0}))}}function Ot(s){let p=0;for(const d of s.systems)p+=d.maxParticles??1e4;return p}function Ht({preset:s,position:p,positionRef:d,autoPlay:V=!0,visible:S=!0,scale:Y=1,resetKey:$=0,onComplete:Z}){var Fe;const[N,se]=i.useState(typeof s=="string"?null:s);i.useEffect(()=>{typeof s=="string"?(se(null),Qe.fetchPreset(s).then(se).catch(l=>{console.error("[HZParticlesFX] Failed to fetch preset:",l)})):se(s)},[s]);const{camera:oe,gl:Le}=ke.useThree(),pe=i.useRef(null);if(!pe.current){const l=(Fe=Le.backend)==null?void 0:Fe.device;l&&(pe.current=l)}const Ce=pe.current,De=i.useRef(null),he=i.useRef(null),Ve=i.useRef(!1),O=i.useRef(!1),y=i.useMemo(()=>N?At(N,Y):null,[N,Y]),qe=i.useMemo(()=>(y==null?void 0:y.systems.every(l=>l.emissionTrailEnabled))??!1,[y]),Ye=i.useMemo(()=>y?y.systems.every(l=>l.emissionTrailEnabled)?1:Ot(y):0,[y]),We=i.useMemo(()=>p?p instanceof c.Vector3?p.clone():new c.Vector3(...p):new c.Vector3(0,0,0),[p]),ye=i.useMemo(()=>(y==null?void 0:y.systems.some(l=>l.emissionTrailEnabled))??!1,[y]),Ue=i.useMemo(()=>new c.PlaneGeometry(1,1),[]),o=i.useMemo(()=>{const l=document.createElement("canvas");l.width=64,l.height=64;const C=l.getContext("2d");C.clearRect(0,0,64,64);const q=C.createRadialGradient(32,32,0,32,32,32);q.addColorStop(0,"rgba(255,255,255,1)"),q.addColorStop(.7,"rgba(255,255,255,1)"),q.addColorStop(1,"rgba(255,255,255,0)"),C.fillStyle=q,C.beginPath(),C.arc(32,32,32,0,Math.PI*2),C.fill();const z=new c.CanvasTexture(l);z.flipY=!1;const G=new c.MeshBasicMaterial({color:16777215,transparent:!0,depthWrite:!1,alphaTest:.01,map:z,vertexColors:!0,side:c.DoubleSide,blending:c.AdditiveBlending});return G.needsUpdate=!0,G},[]),H=64,Me=i.useRef(null),k=i.useRef(0),v=512,R=i.useRef({buf:new Float32Array(v*6),head:0,count:0,elapsed:0}),{trailGeo:ne,trailPosAttr:ie,trailColAttr:w}=i.useMemo(()=>{const C=8*H*6,q=new Float32Array(C*3),z=new Float32Array(C*3),G=new c.BufferGeometry,me=new c.BufferAttribute(q,3);me.setUsage(c.DynamicDrawUsage),G.setAttribute("position",me);const xe=new c.BufferAttribute(z,3);return xe.setUsage(c.DynamicDrawUsage),G.setAttribute("color",xe),G.setDrawRange(0,0),{trailGeo:G,trailPosAttr:me,trailColAttr:xe}},[]),W=i.useMemo(()=>new c.MeshBasicMaterial({color:16777215,vertexColors:!0,transparent:!0,depthWrite:!1,side:c.DoubleSide,blending:c.AdditiveBlending}),[]);i.useEffect(()=>{if(!Ce||!y)return;const l=new Qe.ParticleSystemManager(Ce);return he.current=l,O.current=!1,l.replaceSystems(y).then(()=>{V&&S&&(l.respawnAllSystems(),O.current=!0)}).catch(C=>{console.error("[HZParticlesFX] replaceSystems FAILED:",C)}),()=>{typeof l.destroy=="function"&&l.destroy(),he.current=null}},[Ce,y]),i.useEffect(()=>{const l=he.current;l&&V&&S&&!O.current&&(l.respawnAllSystems(),O.current=!0,Ve.current=!1)},[V,S]);const de=i.useRef(null),ge=i.useRef($);i.useEffect(()=>{if($===ge.current)return;ge.current=$;const l=R.current;l.head=0,l.count=0,l.elapsed=0,ne.setDrawRange(0,0),de.current=null;const C=he.current;C&&(C.respawnAllSystems(),O.current=!0,Ve.current=!1)},[$]);const Se=i.useMemo(()=>new c.Matrix4,[]),Be=i.useMemo(()=>new c.Vector3,[]),ae=i.useMemo(()=>new c.Vector3,[]),ce=i.useMemo(()=>new c.Quaternion,[]),K=i.useMemo(()=>new c.Vector3,[]),Oe=i.useMemo(()=>new c.Vector3(0,0,1),[]),F=i.useMemo(()=>new c.Color,[]);return ke.useFrame((l,C)=>{var Ke;if(!S||!De.current||!he.current)return;const q=he.current,z=(d==null?void 0:d.current)??We,G=C>1?C/1e3:C,me=de.current,xe=me&&G>0,Tt=xe?(z.x-me.x)/G:0,Rt=xe?(z.y-me.y)/G:0,Pt=xe?(z.z-me.z)/G:0;de.current={x:z.x,y:z.y,z:z.z};for(const{system:t,config:a}of q.particleSystems)a.emissionTrailEnabled?(t.setSimulationTransform({position:[z.x,z.y,z.z],velocity:[Tt,Rt,Pt]}),a.shapeTranslationX=0,a.shapeTranslationY=0,a.shapeTranslationZ=0):(a.shapeTranslationX=z.x,a.shapeTranslationY=z.y,a.shapeTranslationZ=z.z);q.updateAllSystems(G);for(const{system:t}of q.particleSystems)t.readbackAndProcessParticles();if(ye){const t=R.current;t.elapsed+=G;let a=0,P=0;const U=(Ke=q.particleSystems.find(({config:j})=>j.emissionTrailEnabled))==null?void 0:Ke.config;if(U){const j=U.emissionTrailShape??"straight";if(j!=="straight"){const B=U.emissionTrailShapeAmplitude??.1,le=U.emissionTrailShapeFrequency??4,te=2*Math.PI*le*t.elapsed;j==="zigzag"?a=B*Math.sign(Math.sin(te)):j==="sine"?a=B*Math.sin(te):j==="spiral"&&(a=B*Math.sin(te),P=B*Math.cos(te))}}const ee=t.head*6;t.buf[ee]=z.x,t.buf[ee+1]=z.y,t.buf[ee+2]=z.z,t.buf[ee+3]=t.elapsed,t.buf[ee+4]=a,t.buf[ee+5]=P,t.head=(t.head+1)%v,t.count<v&&t.count++}const be=De.current;let ve=0;for(let t=0;t<q.particleSystems.length;t++){const{system:a,config:P}=q.particleSystems[t];if(P.emissionTrailEnabled)continue;const U=a.particleData,ee=a.activeParticles;if(!(!U||ee<=0))for(let j=0;j<ee;j++){const B=j*8,le=U[B+0],te=U[B+1],D=U[B+2];let we=U[B+3],ze=U[B+4],Q=U[B+5];const Ae=U[B+6],re=U[B+7];if(Ae>=re||re<=0)continue;const g=Ae/re;if(P.colorTransitionEnabled){const I=P.startColor??[1,0,0],ue=P.endColor??[0,0,1];we=I[0]+(ue[0]-I[0])*g,ze=I[1]+(ue[1]-I[1])*g,Q=I[2]+(ue[2]-I[2])*g}const Te=P.bloomIntensity??1;we=Math.min(1,we*Te),ze=Math.min(1,ze*Te),Q=Math.min(1,Q*Te);const Re=zt(P,Ae,re,j);we*=Re,ze*=Re,Q*=Re;const J=wt(P,Ae,re,j);if(!(J<=0)){if(Be.set(le,te,D),ae.subVectors(oe.position,Be).normalize(),ce.setFromUnitVectors(Oe,ae),P.velocityStretchEnabled&&a.particleVelocities){const I=j*4,ue=a.particleVelocities[I],Ie=a.particleVelocities[I+1],E=a.particleVelocities[I+2],fe=Math.sqrt(ue*ue+Ie*Ie+E*E);if(fe>.001){const Ne=P.velocityStretchFactor??1,X=J*(1+fe*Ne);K.set(J,X,1),ae.set(ue,Ie,E).normalize(),ce.setFromUnitVectors(Oe,ae);const n=(X-J)*.5;Be.addScaledVector(ae,n)}else K.set(J,J,1)}else K.set(J,J,1);Se.compose(Be,ce,K),be.setMatrixAt(ve,Se),F.setRGB(we,ze,Q),be.setColorAt(ve,F),ve++}}}be.count=ve,be.visible=ve>0,ve>0&&(be.instanceMatrix.needsUpdate=!0,be.instanceColor&&(be.instanceColor.needsUpdate=!0));const $e=ve>0||q.particleSystems.some(({system:t})=>t.emitting||t.activeParticles>0);if(Ve.current&&!$e&&(O.current=!1,Z==null||Z()),Ve.current=$e,ye&&Me.current){const t=ie.array,a=w.array;let P=0;const U=oe.position.x,ee=oe.position.y,j=oe.position.z;for(let le=0;le<q.particleSystems.length;le++){const{system:te,config:D}=q.particleSystems[le];if(!D.emissionTrailEnabled||!te.emitting&&te.activeParticles<=0)continue;const we=D.emissionTrailDuration??1,ze=D.emissionTrailWidth??.3,Q=D.bloomIntensity??1,Ae=ze*.5,re=R.current,g=re.buf,Te=re.count,Re=re.head,J=re.elapsed;if(Te<2)continue;const I=((Re-Te)%v+v)%v,ue=J-g[I*6+3],Ie=Math.min(we,ue);if(Ie<.001)continue;const E=D.startColor??[1,1,1],fe=D.colorTransitionEnabled?D.endColor??[1,1,1]:E,Ne=(e,r)=>{const h=J-e;for(let u=0;u<Te-1;u++){const x=((Re-1-u)%v+v)%v,T=((Re-2-u)%v+v)%v,_=g[x*6+3],A=g[T*6+3];if(h>=A&&h<=_){const m=_!==A?(h-A)/(_-A):0;r[0]=g[T*6]+(g[x*6]-g[T*6])*m,r[1]=g[T*6+1]+(g[x*6+1]-g[T*6+1])*m,r[2]=g[T*6+2]+(g[x*6+2]-g[T*6+2])*m,r[3]=g[T*6+4]+(g[x*6+4]-g[T*6+4])*m,r[4]=g[T*6+5]+(g[x*6+5]-g[T*6+5])*m;return}}r[0]=g[I*6],r[1]=g[I*6+1],r[2]=g[I*6+2],r[3]=g[I*6+4],r[4]=g[I*6+5]},X=Math.max(1,Math.min(H,D.emissionTrailSegments??8)),n=[z.x,z.y,z.z],He=D.emissionTrailShape??"straight",Ge=[];{const e=D.emissionTrailShapeAmplitude??.1,r=D.emissionTrailShapeFrequency??4,h=2*Math.PI*r*J;let u=0,x=0;He==="zigzag"?u=e*Math.sign(Math.sin(h)):He==="sine"?u=e*Math.sin(h):He==="spiral"&&(u=e*Math.sin(h),x=e*Math.cos(h)),Ge.push(u,x)}const Ee=[0,0,0,0,0];for(let e=1;e<=X;e++){const r=e/X*Ie;Ne(r,Ee),n.push(Ee[0],Ee[1],Ee[2]),Ge.push(Ee[3],Ee[4])}const et=n[0]-n[X*3],tt=n[1]-n[X*3+1],st=n[2]-n[X*3+2];if(et*et+tt*tt+st*st<1e-6)continue;let nt=0,rt=0,ot=0;const Pe=[],Xe=[];for(let e=0;e<=X;e++){let r,h,u;e===0?(r=n[3]-n[0],h=n[4]-n[1],u=n[5]-n[2]):e===X?(r=n[e*3]-n[(e-1)*3],h=n[e*3+1]-n[(e-1)*3+1],u=n[e*3+2]-n[(e-1)*3+2]):(r=n[(e+1)*3]-n[(e-1)*3],h=n[(e+1)*3+1]-n[(e-1)*3+1],u=n[(e+1)*3+2]-n[(e-1)*3+2]);let x=Math.sqrt(r*r+h*h+u*u);x<1e-8&&(r=0,h=0,u=1,x=1),r/=x,h/=x,u/=x,Xe.push(r,h,u);const T=U-n[e*3],_=ee-n[e*3+1],A=j-n[e*3+2];let m=h*A-u*_,M=u*T-r*A,b=r*_-h*T,L=Math.sqrt(m*m+M*M+b*b);L<1e-8&&(m=-u,M=0,b=r,L=Math.sqrt(m*m+M*M+b*b)),L<1e-8&&(m=0,M=1,b=0,L=1),m/=L,M/=L,b/=L,e>0&&m*nt+M*rt+b*ot<0&&(m=-m,M=-M,b=-b),nt=m,rt=M,ot=b,Pe.push(m,M,b)}if(He!=="straight")for(let e=1;e<=X;e++){const r=Ge[e*2],h=Ge[e*2+1];if(Math.abs(r)<1e-8&&Math.abs(h)<1e-8)continue;const u=e*3,x=Xe[u],T=Xe[u+1],_=Xe[u+2];let A=-_,m=0,M=x,b=Math.sqrt(A*A+M*M);if(b<1e-6&&(A=0,m=_,M=-T,b=Math.sqrt(A*A+m*m+M*M),b<1e-6&&(A=1,m=0,M=0,b=1)),A/=b,m/=b,M/=b,He==="spiral"){const L=T*M-_*m,je=_*A-x*M,Ze=x*m-T*A;n[u]+=A*r+L*h,n[u+1]+=m*r+je*h,n[u+2]+=M*r+Ze*h}else n[u]+=A*r,n[u+1]+=m*r,n[u+2]+=M*r}for(let e=0;e<X;e++){const r=e/X,h=(e+1)/X,u=Ae*(1-r),x=Ae*(1-h),T=(1-r)*(1-r),_=(1-h)*(1-h),A=Math.min(1,(E[0]+(fe[0]-E[0])*r)*Q)*T,m=Math.min(1,(E[1]+(fe[1]-E[1])*r)*Q)*T,M=Math.min(1,(E[2]+(fe[2]-E[2])*r)*Q)*T,b=Math.min(1,(E[0]+(fe[0]-E[0])*h)*Q)*_,L=Math.min(1,(E[1]+(fe[1]-E[1])*h)*Q)*_,je=Math.min(1,(E[2]+(fe[2]-E[2])*h)*Q)*_,Ze=n[e*3],it=n[e*3+1],at=n[e*3+2],ct=n[(e+1)*3],lt=n[(e+1)*3+1],ut=n[(e+1)*3+2],ft=Pe[e*3],pt=Pe[e*3+1],dt=Pe[e*3+2],mt=Pe[(e+1)*3],ht=Pe[(e+1)*3+1],yt=Pe[(e+1)*3+2],Ct=Ze+ft*u,Dt=it+pt*u,Vt=at+dt*u,Mt=Ze-ft*u,gt=it-pt*u,St=at-dt*u,xt=ct+mt*x,bt=lt+ht*x,vt=ut+yt*x,Ft=ct-mt*x,It=lt-ht*x,Et=ut-yt*x,f=P*3;t[f]=Ct,t[f+1]=Dt,t[f+2]=Vt,a[f]=A,a[f+1]=m,a[f+2]=M,t[f+3]=Mt,t[f+4]=gt,t[f+5]=St,a[f+3]=A,a[f+4]=m,a[f+5]=M,t[f+6]=xt,t[f+7]=bt,t[f+8]=vt,a[f+6]=b,a[f+7]=L,a[f+8]=je,t[f+9]=Mt,t[f+10]=gt,t[f+11]=St,a[f+9]=A,a[f+10]=m,a[f+11]=M,t[f+12]=Ft,t[f+13]=It,t[f+14]=Et,a[f+12]=b,a[f+13]=L,a[f+14]=je,t[f+15]=xt,t[f+16]=bt,t[f+17]=vt,a[f+15]=b,a[f+16]=L,a[f+17]=je,P+=6}}const B=k.current;if(P<B){const le=P*3,te=B*3;for(let D=le;D<te;D++)t[D]=0,a[D]=0}k.current=P,ne.setDrawRange(0,P),ie.needsUpdate=!0,w.needsUpdate=!0}}),N?_e.jsxs(_e.Fragment,{children:[_e.jsx("instancedMesh",{ref:l=>{De.current=l,l&&(l.count=0,l.visible=!qe)},args:[Ue,o,Ye],frustumCulled:!1,renderOrder:100,visible:S&&!qe}),ye&&_e.jsx("mesh",{ref:Me,geometry:ne,material:W,frustumCulled:!1,renderOrder:99,visible:S})]}):null}const jt=`
1
+ "use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const s=require("react"),Z=require("@react-three/fiber"),X=require("hz-particles"),K=require("react/jsx-runtime"),Q=require("three");function Y(n){const t=Object.create(null,{[Symbol.toStringTag]:{value:"Module"}});if(n){for(const a in n)if(a!=="default"){const e=Object.getOwnPropertyDescriptor(n,a);Object.defineProperty(t,a,e.get?e:{enumerable:!0,get:()=>n[a]})}}return t.default=n,Object.freeze(t)}const p=Y(Q);function J(n,t){if(t===1)return n;const a=(e,i)=>e!=null?e*t:i*t;return{...n,systems:n.systems.map(e=>({...e,particleSize:a(e.particleSize,.5),minSize:e.minSize!=null?e.minSize*t:void 0,maxSize:e.maxSize!=null?e.maxSize*t:void 0,particleSpeed:(e.particleSpeed??1)*t,minSpeed:e.minSpeed!=null?e.minSpeed*t:void 0,maxSpeed:e.maxSpeed!=null?e.maxSpeed*t:void 0,cubeLength:e.cubeLength!=null?e.cubeLength*t:void 0,outerLength:e.outerLength!=null?e.outerLength*t:void 0,innerLength:e.innerLength!=null?e.innerLength*t:void 0,outerRadius:e.outerRadius!=null?e.outerRadius*t:void 0,innerRadius:e.innerRadius!=null?e.innerRadius*t:void 0,squareSize:e.squareSize!=null?e.squareSize*t:void 0,squareInnerSize:e.squareInnerSize!=null?e.squareInnerSize*t:void 0,circleInnerRadius:e.circleInnerRadius!=null?e.circleInnerRadius*t:void 0,circleOuterRadius:e.circleOuterRadius!=null?e.circleOuterRadius*t:void 0,cylinderInnerRadius:e.cylinderInnerRadius!=null?e.cylinderInnerRadius*t:void 0,cylinderOuterRadius:e.cylinderOuterRadius!=null?e.cylinderOuterRadius*t:void 0,cylinderHeight:e.cylinderHeight!=null?e.cylinderHeight*t:void 0,emissionTrailWidth:(e.emissionTrailWidth??.3)*t,emissionTrailDuration:(e.emissionTrailDuration??1)*t,emissionTrailMinDistance:e.emissionTrailMinDistance!=null?e.emissionTrailMinDistance*t:void 0,emissionTrailShapeAmplitude:e.emissionTrailShapeAmplitude!=null?e.emissionTrailShapeAmplitude*t:void 0}))}}function $({preset:n,positionRef:t,position:a=[0,0,0],scale:e=1,active:i=!0,renderPriority:z=1,noOcclusion:A=!1}){const d=s.useRef(null),v=s.useRef(null),x=s.useRef(!1),I=s.useRef(0),C=s.useRef(i);C.current=i;const y=s.useRef(a);y.current=a;const D=s.useMemo(()=>e&&e!==1?J(n,e):n,[n,e]),E=l=>t?l.addMovingEmitter(D,{getPosition:()=>{const m=t.current;return C.current&&m?[m.x,m.y,m.z]:null}}):l.addEmitter(D,y.current);return s.useEffect(()=>{var f,u;const l=d.current;if(!l)return;let m=!1;return(u=(f=v.current)==null?void 0:f.remove)==null||u.call(f),v.current=null,E(l).then(c=>{var T;m?(T=c==null?void 0:c.remove)==null||T.call(c):v.current=c}),()=>{m=!0}},[D]),s.useEffect(()=>()=>{var l,m;(m=(l=d.current)==null?void 0:l.destroy)==null||m.call(l),d.current=null,v.current=null,x.current=!1},[]),Z.useFrame(l=>{l.gl.render(l.scene,l.camera);const m=performance.now(),f=I.current?Math.min(.05,(m-I.current)/1e3):.016;I.current=m;const u=l.gl,c=u.backend;!d.current&&!x.current&&(c!=null&&c.device)&&(c!=null&&c.context)&&(x.current=!0,X.initHzFxOverlay({device:c.device,context:c.context,canvas:u.domElement},{getSceneDepth:A?()=>null:X.makeThreeSceneDepth(u)}).then(R=>(d.current=R,E(R))).then(R=>{v.current=R}).catch(R=>console.error("[HZFaithfulFX] init failed:",R)));const T=d.current;if(T){const R=l.camera;T.setCamera(R.projectionMatrix.elements,R.matrixWorldInverse.elements,R.position),T.render(f)}},z),null}const ee=`
2
2
  attribute vec4 aColor;
3
3
  varying vec4 vColor;
4
4
  void main() {
5
5
  vColor = aColor;
6
6
  gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
7
7
  }
8
- `,_t=`
8
+ `,te=`
9
9
  varying vec4 vColor;
10
10
  void main() {
11
11
  gl_FragColor = vColor;
12
12
  }
13
- `;function Wt({positionRef:s,duration:p=1,width:d=.3,color:V=[1,1,1],maxPoints:S=256,minDistance:Y=.05,opacity:$=1,blending:Z=c.AdditiveBlending,visible:N=!0}){const{camera:se}=ke.useThree(),oe=i.useRef(null),Le=i.useRef({positions:new Float32Array(S*3),times:new Float32Array(S),head:0,count:0,lastPos:new c.Vector3(1/0,1/0,1/0),elapsed:0}),{geometry:pe,posAttr:Ce,colorAttr:De,indexBuf:he}=i.useMemo(()=>{const ye=S*2,Ue=new Float32Array(ye*3),o=new Float32Array(ye*4),H=new c.BufferGeometry,Me=new c.BufferAttribute(Ue,3);Me.setUsage(c.DynamicDrawUsage),H.setAttribute("position",Me);const k=new c.BufferAttribute(o,4);k.setUsage(c.DynamicDrawUsage),H.setAttribute("aColor",k);const v=(S-1)*6,R=new Uint32Array(v);for(let ie=0;ie<S-1;ie++){const w=ie*2,W=ie*6;R[W]=w,R[W+1]=w+1,R[W+2]=w+2,R[W+3]=w+1,R[W+4]=w+3,R[W+5]=w+2}const ne=new c.BufferAttribute(R,1);return H.setIndex(ne),H.setDrawRange(0,0),{geometry:H,posAttr:Me,colorAttr:k,indexBuf:ne}},[S]),Ve=i.useMemo(()=>new c.ShaderMaterial({vertexShader:jt,fragmentShader:_t,transparent:!0,depthWrite:!1,side:c.DoubleSide,blending:Z}),[Z]),O=i.useMemo(()=>new c.Vector3,[]),y=i.useMemo(()=>new c.Vector3,[]),qe=i.useMemo(()=>new c.Vector3,[]),Ye=i.useMemo(()=>new c.Vector3(0,1,0),[]),We=i.useMemo(()=>new c.Vector3,[]);return ke.useFrame((ye,Ue)=>{if(!oe.current)return;const o=Le.current;if(o.elapsed+=Ue,!N){o.count=0,o.head=0,o.lastPos.set(1/0,1/0,1/0),pe.setDrawRange(0,0);return}const H=s.current;if(!H)return;if(o.lastPos.distanceTo(H)>=Y){const w=(o.head+o.count)%S;o.positions[w*3]=H.x,o.positions[w*3+1]=H.y,o.positions[w*3+2]=H.z,o.times[w]=o.elapsed,o.count<S?o.count++:o.head=(o.head+1)%S,o.lastPos.copy(H)}for(;o.count>0;){const w=o.head;if(o.elapsed-o.times[w]>p)o.head=(o.head+1)%S,o.count--;else break}if(o.count<2){pe.setDrawRange(0,0);return}const k=Ce.array,v=De.array;let R=null;const ne=o.count;for(let w=0;w<ne;w++){const W=(o.head+w)%S,de=o.positions[W*3],ge=o.positions[W*3+1],Se=o.positions[W*3+2],ae=1-(o.elapsed-o.times[W])/p;if(w<ne-1){const Fe=(o.head+w+1)%S;O.set(o.positions[Fe*3]-de,o.positions[Fe*3+1]-ge,o.positions[Fe*3+2]-Se);const l=O.length();l>1e-6?O.divideScalar(l):R&&O.copy(R)}else R&&O.copy(R);qe.set(se.position.x-de,se.position.y-ge,se.position.z-Se),y.crossVectors(O,qe);let ce=y.length();ce<1e-6&&(We.crossVectors(O,Ye),y.copy(We),ce=y.length(),ce<1e-6&&(y.set(1,0,0),ce=1)),y.divideScalar(ce);const K=d*ae,Oe=$*ae*ae,F=w*2;k[F*3]=de+y.x*K,k[F*3+1]=ge+y.y*K,k[F*3+2]=Se+y.z*K,k[(F+1)*3]=de-y.x*K,k[(F+1)*3+1]=ge-y.y*K,k[(F+1)*3+2]=Se-y.z*K,v[F*4]=V[0],v[F*4+1]=V[1],v[F*4+2]=V[2],v[F*4+3]=Oe,v[(F+1)*4]=V[0],v[(F+1)*4+1]=V[1],v[(F+1)*4+2]=V[2],v[(F+1)*4+3]=Oe,R=R||new c.Vector3,R.copy(O)}const ie=(ne-1)*6;pe.setDrawRange(0,ie),Ce.needsUpdate=!0,De.needsUpdate=!0}),_e.jsx("mesh",{ref:oe,geometry:pe,material:Ve,frustumCulled:!1,renderOrder:101,visible:N})}Object.defineProperty(exports,"fetchPreset",{enumerable:!0,get:()=>Qe.fetchPreset});exports.HZParticlesFX=Ht;exports.HZTrailRibbon=Wt;exports.computeParticleOpacity=zt;exports.computeParticleSize=wt;exports.particleHash=Je;exports.scalePreset=At;
13
+ `;function ne({positionRef:n,duration:t=1,width:a=.3,color:e=[1,1,1],maxPoints:i=256,minDistance:z=.05,opacity:A=1,blending:d=p.AdditiveBlending,visible:v=!0}){const{camera:x}=Z.useThree(),I=s.useRef(null),C=s.useRef({positions:new Float32Array(i*3),times:new Float32Array(i),head:0,count:0,lastPos:new p.Vector3(1/0,1/0,1/0),elapsed:0}),{geometry:y,posAttr:D,colorAttr:E,indexBuf:l}=s.useMemo(()=>{const B=i*2,U=new Float32Array(B*3),r=new Float32Array(B*4),M=new p.BufferGeometry,H=new p.BufferAttribute(U,3);H.setUsage(p.DynamicDrawUsage),M.setAttribute("position",H);const w=new p.BufferAttribute(r,4);w.setUsage(p.DynamicDrawUsage),M.setAttribute("aColor",w);const b=(i-1)*6,h=new Uint32Array(b);for(let F=0;F<i-1;F++){const o=F*2,g=F*6;h[g]=o,h[g+1]=o+1,h[g+2]=o+2,h[g+3]=o+1,h[g+4]=o+3,h[g+5]=o+2}const O=new p.BufferAttribute(h,1);return M.setIndex(O),M.setDrawRange(0,0),{geometry:M,posAttr:H,colorAttr:w,indexBuf:O}},[i]),m=s.useMemo(()=>new p.ShaderMaterial({vertexShader:ee,fragmentShader:te,transparent:!0,depthWrite:!1,side:p.DoubleSide,blending:d}),[d]),f=s.useMemo(()=>new p.Vector3,[]),u=s.useMemo(()=>new p.Vector3,[]),c=s.useMemo(()=>new p.Vector3,[]),T=s.useMemo(()=>new p.Vector3(0,1,0),[]),R=s.useMemo(()=>new p.Vector3,[]);return Z.useFrame((B,U)=>{if(!I.current)return;const r=C.current;if(r.elapsed+=U,!v){r.count=0,r.head=0,r.lastPos.set(1/0,1/0,1/0),y.setDrawRange(0,0);return}const M=n.current;if(!M)return;if(r.lastPos.distanceTo(M)>=z){const o=(r.head+r.count)%i;r.positions[o*3]=M.x,r.positions[o*3+1]=M.y,r.positions[o*3+2]=M.z,r.times[o]=r.elapsed,r.count<i?r.count++:r.head=(r.head+1)%i,r.lastPos.copy(M)}for(;r.count>0;){const o=r.head;if(r.elapsed-r.times[o]>t)r.head=(r.head+1)%i,r.count--;else break}if(r.count<2){y.setDrawRange(0,0);return}const w=D.array,b=E.array;let h=null;const O=r.count;for(let o=0;o<O;o++){const g=(r.head+o)%i,L=r.positions[g*3],V=r.positions[g*3+1],j=r.positions[g*3+2],_=1-(r.elapsed-r.times[g])/t;if(o<O-1){const W=(r.head+o+1)%i;f.set(r.positions[W*3]-L,r.positions[W*3+1]-V,r.positions[W*3+2]-j);const N=f.length();N>1e-6?f.divideScalar(N):h&&f.copy(h)}else h&&f.copy(h);c.set(x.position.x-L,x.position.y-V,x.position.z-j),u.crossVectors(f,c);let P=u.length();P<1e-6&&(R.crossVectors(f,T),u.copy(R),P=u.length(),P<1e-6&&(u.set(1,0,0),P=1)),u.divideScalar(P);const q=a*_,G=A*_*_,S=o*2;w[S*3]=L+u.x*q,w[S*3+1]=V+u.y*q,w[S*3+2]=j+u.z*q,w[(S+1)*3]=L-u.x*q,w[(S+1)*3+1]=V-u.y*q,w[(S+1)*3+2]=j-u.z*q,b[S*4]=e[0],b[S*4+1]=e[1],b[S*4+2]=e[2],b[S*4+3]=G,b[(S+1)*4]=e[0],b[(S+1)*4+1]=e[1],b[(S+1)*4+2]=e[2],b[(S+1)*4+3]=G,h=h||new p.Vector3,h.copy(f)}const F=(O-1)*6;y.setDrawRange(0,F),D.needsUpdate=!0,E.needsUpdate=!0}),K.jsx("mesh",{ref:I,geometry:y,material:m,frustumCulled:!1,renderOrder:101,visible:v})}function k(n){return Math.abs(Math.sin(n*12.9898)*43758.5453)%1}function re(n){const t=Math.sin(n*54321.67)*43758.5453%1;return t<0?t+1:t}function ie(n,t,a,e){if(a<=0)return 0;const i=t/a;let z=n.particleSize??.5;if(n.randomSize){const d=n.minSize??.1,v=n.maxSize??.5;z=d+(v-d)*re(a)}const A=Math.max(.01,Math.min(10,n.sizeLifetimeSpeed??1));if(n.fadeSizeEnabled&&(z*=1-Math.pow(i,1/A)),n.increaseSizeEnabled&&(z*=1+Math.pow(i,1/A)),n.pulseEnabled){const d=n.pulseAmplitude??.5,v=n.pulseFrequency??1,I=(n.pulsePhaseRandom??0)*k(e)*Math.PI*2;z*=1+d*Math.sin(t*v*Math.PI*2+I)}return Math.max(0,z)}function oe(n,t,a,e){if(a<=0)return 0;let i=n.opacity??1;const z=t/a;if(n.fadeEnabled&&(i*=Math.max(0,1-z)),n.pulseEnabled&&n.pulseOpacity){const A=n.pulseAmplitude??.5,d=n.pulseFrequency??1,x=(n.pulsePhaseRandom??0)*k(e)*Math.PI*2;i*=Math.max(0,1+A*Math.sin(t*d*Math.PI*2+x))}return Math.max(0,Math.min(1,i))}Object.defineProperty(exports,"fetchPreset",{enumerable:!0,get:()=>X.fetchPreset});exports.HZFaithfulFX=$;exports.HZTrailRibbon=ne;exports.computeParticleOpacity=oe;exports.computeParticleSize=ie;exports.particleHash=k;exports.scalePreset=J;
@@ -2,26 +2,31 @@ import type { SceneData, ParticleSystemConfig } from 'hz-particles';
2
2
  import type { RefObject } from 'react';
3
3
  import type { Vector3 } from 'three';
4
4
 
5
- export interface HZParticlesFXProps {
6
- /** Preset SceneData (loaded from JSON or inline) or URL string to fetch. */
7
- preset: SceneData | string;
8
- /** Static emitter position [x, y, z] or THREE.Vector3. Ignored when positionRef is provided. */
9
- position?: [number, number, number] | Vector3;
10
- /** Reactive ref whose .current is updated every frame (for moving emitters / trails). */
11
- positionRef?: RefObject<Vector3>;
12
- /** Automatically start emission on mount (default true). */
13
- autoPlay?: boolean;
14
- /** Toggle visibility (default true). */
15
- visible?: boolean;
16
- /** Scale multiplier for particle size and speed (default 1). */
5
+ export interface HZFaithfulFXProps {
6
+ /** Preset SceneData (loaded from JSON or inline). */
7
+ preset: SceneData;
8
+ /** Moving emitter (e.g. a ball trail): the overlay reads this ref each frame. */
9
+ positionRef?: RefObject<Vector3 | null>;
10
+ /** Static emitter world position (used when `positionRef` is omitted). */
11
+ position?: [number, number, number];
12
+ /** Uniform scale applied to the preset (sizes, speeds, emission, trail). */
17
13
  scale?: number;
18
- /** Change this value to clear trail history and respawn particles (e.g. increment on new shot). */
19
- resetKey?: number;
20
- /** Called once all systems have finished emitting and all particles are dead. */
21
- onComplete?: () => void;
14
+ /** When false, a moving emitter pauses (no new trail). Default true. */
15
+ active?: boolean;
16
+ /** `useFrame` priority. This component DRIVES rendering, so keep it > 0. Default 1. */
17
+ renderPriority?: number;
18
+ /** Skip scene-depth occlusion (particles never hidden behind geometry). */
19
+ noOcclusion?: boolean;
22
20
  }
23
21
 
24
- export declare function HZParticlesFX(props: HZParticlesFXProps): JSX.Element;
22
+ /**
23
+ * Engine-FAITHFUL R3F component: runs the real hz-particles overlay inline on three's
24
+ * WebGPURenderer (shader shapes, noise, per-system blending, multi-pass bloom, faithful
25
+ * trails) — what you design in the editor is exactly what renders. It DRIVES rendering
26
+ * (its `useFrame` calls `gl.render` itself). If your host owns its own render loop, use
27
+ * `initHzFxOverlay()` + `makeThreeSceneDepth()` directly instead.
28
+ */
29
+ export declare function HZFaithfulFX(props: HZFaithfulFXProps): JSX.Element;
25
30
 
26
31
  export declare function scalePreset(preset: SceneData, scale: number): SceneData;
27
32