import { Entity } from './Entity'; import { IRenderer } from '../renderer/IRenderer'; import type { ParticleBackend } from '../wasm/particle-backend'; /** * Options for configuring a {@link ComputeParticleEntity}. */ export interface ComputeParticleOptions { /** Maximum number of particles to simulate. Defaults to 10000. */ maxParticles?: number; /** Spring stiffness coefficient for returning to origin. Defaults to 0.05. */ springK?: number; /** Velocity damping factor in `[0, 1]`. Defaults to 0.95. */ damping?: number; /** Bounce damping factor for boundary collisions in `[0, 1]`. Defaults to 0.5. */ bounceDamping?: number; /** Speed limit for particles. Defaults to 500.0. */ maxVelocity?: number; /** Base particle size in pixels. Defaults to 4. */ size?: number; /** CSS color string for the particles. Defaults to '#00f0ff'. */ color?: string; /** Whether the particle layer captures pointer/hit events. Defaults to false. */ pointerEvents?: boolean; } export declare const PARTICLE_STRIDE_FLOATS = 8; export declare const PARTICLE_OFFSET_POSITION_X = 0; export declare const PARTICLE_OFFSET_POSITION_Y = 1; export declare const PARTICLE_OFFSET_VELOCITY_X = 2; export declare const PARTICLE_OFFSET_VELOCITY_Y = 3; export declare const PARTICLE_OFFSET_ORIGIN_X = 4; export declare const PARTICLE_OFFSET_ORIGIN_Y = 5; export declare const PARTICLE_OFFSET_SIZE = 6; export declare const PARTICLE_OFFSET_LIFE = 7; /** * An entity representing a high-performance WebGPU/CPU particle simulation layer. */ export declare class ComputeParticleEntity extends Entity { maxParticles: number; springK: number; damping: number; bounceDamping: number; maxVelocity: number; size: number; baseColor: string; pointerEvents: boolean; /** Flat array containing layout of all particles: position, velocity, origin, size, life. */ particleData: Float32Array; /** Flag indicating whether the particle coordinates need to be initialized. */ needsInit: boolean; /** Active explosion impulse to apply in the next simulation step. */ pendingExplosion: { x: number; y: number; force: number; } | null; /** WebGPU storage buffer containing particle states. */ gpuStorageBuffer: any; /** WebGPU uniform buffer containing simulation parameters. */ gpuUniformBuffer: any; /** WebGPU bind group for the compute shader pass. */ computeBindGroup: any; /** WebGPU bind group for the render pass (usually same as compute). */ renderBindGroup: any; /** When the last simulation step ran through the WASM backend, its fused * pending-animation flag; `null` when the last step used the JS `updateCPU` * path (so `hasPendingAnimations` falls back to its own scan). */ private _wasmPending; constructor(options?: ComputeParticleOptions); /** * Disperses all particles randomly across the specified screen bounds. * Sets initial positions, velocities, origins, and sizes. * * @param width - Simulation zone width. * @param height - Simulation zone height. */ initRandomParticles(width: number, height: number): void; /** * Sets the origins (ox, oy) for a subset or all particles. * Also sets position to origin if requestPositionReset is true. * * @param points - Flat Float32Array containing [x0, y0, x1, y1, ...] * @param requestPositionReset - Whether to set current positions to the new origins. Defaults to true. */ setOrigins(points: Float32Array | number[], requestPositionReset?: boolean): void; /** * Sets the current positions (x, y) for a subset or all particles. * * @param positions - Flat Float32Array containing [x0, y0, x1, y1, ...] */ setPositions(positions: Float32Array | number[]): void; /** * Sets the current velocities (vx, vy) for a subset or all particles. * * @param velocities - Flat Float32Array containing [vx0, vy0, vx1, vy1, ...] */ setVelocities(velocities: Float32Array | number[]): void; /** * Triggers an explosion force center. * * @param x - Explosion center x-coordinate. * @param y - Explosion center y-coordinate. * @param force - Magnitude force scalar. */ triggerExplosion(x: number, y: number, force: number): void; isPointInside(_x: number, _y: number): boolean; render(_r: IRenderer): void; /** * True while any live particle still has a visually meaningful velocity or * sits meaningfully away from its spring target. * * The actual per-frame simulation (WebGPU compute pass / {@link updateCPU}) * runs through a dedicated Scene-level pre-pass outside the normal * render-tree walk, but this entity is still a normal tree member that * walk visits — without this override, the base `Entity.hasPendingAnimations()` * (always `false`) is what Scene sees, so `renderMode: 'always'`'s idle * auto-throttle drops the WHOLE scene to the idle FPS floor the instant * nothing else in the tree is animating, even while thousands of particles are visibly * drifting or spring-settling. A spring+damping system asymptotically * approaches zero velocity but never reaches it exactly, so this checks * against a small epsilon rather than `!== 0` — otherwise this would * always return `true` and defeat the idle throttle entirely. */ hasPendingAnimations(): boolean; /** * Updates particle simulation on the CPU. * Handles spring forces, mouse repulsion, explosion impulses, velocity capping, and bounds bouncing/clamping. * * @param dt - Delta time in seconds. * @param mouseX - Mouse x-coordinate, or a value below -9000 if inactive. * @param mouseY - Mouse y-coordinate, or a value below -9000 if inactive. * @param width - Boundary width. * @param height - Boundary height. */ updateCPU(dt: number, mouseX: number, mouseY: number, width: number, height: number): void; /** * Advance the simulation one step through the WASM particle kernel: transpose * this entity's AoS buffer into the backend's SoA views, run `particle_step`, * and scatter position/velocity/life back. Produces an f32 result (matching * the WGSL shader) that differs from {@link updateCPU}'s f64 by <1 ULP/step — * the accepted CPU-vs-GPU-class divergence. Caches the kernel's fused * pending-animation flag so {@link hasPendingAnimations} needs no second scan. * * The backend holds one resident SoA store, so a Scene with multiple particle * entities reuses it sequentially — origin is therefore re-gathered each call * (not upload-once), a couple of extra f32 reads per particle. * * Returns `false` if the kernel declined the call and {@link updateCPU} ran * instead, so the Scene can report which path actually simulated this frame * rather than assuming an installed backend did the work. */ stepWithBackend(backend: ParticleBackend, dt: number, mouseX: number, mouseY: number, width: number, height: number): boolean; destroy(): void; /** * Frees all GPU resources allocated for WebGPU simulation. */ destroyGPUResources(): void; }