import type { ComputeNode, Renderer, StorageBufferNode, UniformNode } from 'three/webgpu'; import type { GPUInteractionCapabilityReport } from '../Interaction/GPUInteractionCapabilityReport.cjs'; import type { GPUInteractionSimulationKind, ParticleInteractionComputeKey, ParticleInteractionWorld, SimulationInteractionOptions } from '../Interaction/GPUInteractionSimulation.cjs'; import type { GPUInteractionWorld } from '../Interaction/GPUInteractionWorld.cjs'; import type { BVHVolumeConstraint } from '../BVHVolumeConstraint.cjs'; import type { SDFVolumeConstraint } from '../SDFVolumeConstraint.cjs'; import type { TSLFloatNode, TSLUintNode, TSLVec3Node } from '../../types/tsl.cjs'; /** Fixed-size CPU vector accepted by particle configuration. */ export type ParticlesVector3 = readonly [x: number, y: number, z: number]; /** Fixed-size numeric range accepted by particle configuration. */ export type ParticlesRange = readonly [minimum: number, maximum: number]; /** Spawn distributions built into {@link Particles}. */ export type ParticlesSpawnShapeType = 'point' | 'sphere' | 'box'; /** Optional overrides for the built-in particle spawn distribution. */ export interface ParticlesSpawnShapeOptions { type?: ParticlesSpawnShapeType | undefined; center?: ParticlesVector3 | undefined; radius?: number | undefined; extents?: ParticlesVector3 | undefined; } /** Fully resolved built-in particle spawn distribution. */ export interface ParticlesSpawnShape { type: ParticlesSpawnShapeType; center: ParticlesVector3; radius: number; extents: ParticlesVector3; } /** Optional overrides for the initial velocity cone. */ export interface ParticlesInitialVelocityOptions { direction?: ParticlesVector3 | undefined; speed?: ParticlesRange | undefined; spread?: number | undefined; } /** Fully resolved initial velocity cone. */ export interface ParticlesInitialVelocity { direction: ParticlesVector3; speed: ParticlesRange; spread: number; } /** Context passed to authored spawn-position and spawn-velocity hooks. */ export interface ParticlesSpawnHookContext { seed01: TSLFloatNode; time: TSLFloatNode; } /** Per-particle context passed to authored force hooks. */ export interface ParticlesForceHookContext extends ParticlesSpawnHookContext { index: TSLUintNode; position: TSLVec3Node; velocity: TSLVec3Node; lifetime: TSLFloatNode; age01: TSLFloatNode; } /** Authored TSL factories used to customize spawn and force behavior. */ export interface ParticlesHooks { spawnPosition?: ((context: ParticlesSpawnHookContext) => TSLVec3Node) | undefined; spawnVelocity?: ((context: ParticlesSpawnHookContext) => TSLVec3Node) | undefined; force?: ((context: ParticlesForceHookContext) => TSLVec3Node) | undefined; } /** External GPU sample source used instead of a built-in spawn shape. */ export interface ParticlesSpawnSource { count: number; samplePosition(indexNode: TSLUintNode): TSLVec3Node; } /** Construction options for {@link Particles}. */ export interface ParticlesOptions { count?: number | undefined; lifetime?: number | ParticlesRange | undefined; emission?: 'rate' | 'burst' | undefined; rate?: number | null | undefined; spawnShape?: ParticlesSpawnShapeOptions | undefined; initialVelocity?: ParticlesInitialVelocityOptions | undefined; gravity?: ParticlesVector3 | undefined; drag?: number | undefined; curlStrength?: number | undefined; curlScale?: number | undefined; sizeRange?: ParticlesRange | undefined; particleRadius?: number | undefined; fixedTimeStep?: number | null | undefined; maxSubsteps?: number | undefined; maxFrameDelta?: number | undefined; spawnSource?: ParticlesSpawnSource | null | undefined; hooks?: ParticlesHooks | undefined; } /** Stable particle storage bus exposed to render and simulation adapters. */ export interface ParticlesBuffers { positions: StorageBufferNode<'vec3'>; velocities: StorageBufferNode<'vec3'>; spawnTimes: StorageBufferNode<'float'>; lifetimes: StorageBufferNode<'float'>; seeds: StorageBufferNode<'float'>; sizes: StorageBufferNode<'float'>; instanceMatrices: StorageBufferNode<'mat4'>; readonly [name: string]: unknown; } /** Stable uniform bus shared by the particle compute passes. */ export interface ParticlesUniforms { now: UniformNode<'float', number>; deltaTime: UniformNode<'float', number>; particleCount: UniformNode<'uint', number>; readonly [name: string]: unknown; } /** Result of resolving a frame delta against the fixed-step accumulator. */ export interface ParticlesStepConfig { steps: number; delta: number; } /** Capability result returned when no interaction world is attached. */ export interface ParticlesDetachedInteractionCapabilityReport { readonly available: null; readonly enabled: boolean; readonly reason: 'no interaction world attached'; } /** Capability result exposed by the particle interaction consumer. */ export type ParticlesInteractionCapabilityReport = ParticlesDetachedInteractionCapabilityReport | Readonly; /** * GPU particle system assembled from the same primitives as {@link Boids} * (instancedArray storage + TSL compute kernels), pinned to the VFX-grammar v1 * feature set: capacity 16–131,072, spawn rate or looping burst, spawn shape * point/sphere/box or an external surface-sample source, lifetime range, * initial velocity cone, built-in gravity/drag/curl options, and three * authorable hooks (`spawnPosition`, `spawnVelocity`, `force`) that receive * per-particle TSL context nodes and return TSL nodes. * * Simulation contract (GOAL_VFX_GRAMMAR.md): * - Deterministic stepping: `step( renderer, externalDeltaSeconds )` bypasses * wall clocks entirely when a delta is injected; all randomness derives from * per-particle hashes — no `Math.random()` anywhere. * - Bus mapping: positions/velocities/ages/lifetimes/seeds/sizes buffers are * exposed under `buffers` for standard-attribute adapters. * - Interaction coupling: `setInteractionWorld()` / `clearInteractionWorld()` * reuse the shared particle collider constraint pass. * * Deliberately NOT in v1 (Wave C): particle↔particle collisions, events, * trails, sub-emitters, sorting. * * @example * const particles = new Particles( { count: 8192, lifetime: [ 0.8, 2.0 ], gravity: [ 0, - 6, 0 ] } ); * // inside the frame loop: * await particles.step( renderer, dtSeconds ); * mesh.instanceMatrix = particles.buffers.instanceMatrices.value; */ export declare class Particles { particleCount: number; particleMaxCount: number; is3D: true; lifetimeRange: [minimum: number, maximum: number]; emission: 'rate' | 'burst'; rate: number; spawnShape: ParticlesSpawnShape; initialVelocity: ParticlesInitialVelocity; gravity: ParticlesVector3; drag: number; curlStrength: number; curlScale: number; sizeRange: ParticlesRange; particleRadius: number; _particleSpacing: number; speedNorm: number; fixedTimeStep: number | null; maxSubsteps: number; spawnSource: ParticlesSpawnSource | null; hooks: ParticlesHooks; interactionWorld: GPUInteractionWorld | null; interaction: Readonly | null; sdfVolumeConstraint: SDFVolumeConstraint | null; bvhVolumeConstraint: BVHVolumeConstraint | null; ubos: ParticlesUniforms; buffers: ParticlesBuffers; _interactionKind: GPUInteractionSimulationKind; _interactionCompute: ComputeNode | null; _interactionComputes: Map | null; _interactionComputeWorld: ParticleInteractionWorld | null; private _maxFrameDelta; private _accumulator; private _externalNow; private _prev; private _gpuInitDone; private _kernelsBuilt; private _computeInit; private _computeUpdate; private _computeMatrices; /** * @param {Object} [options] * @param {number} [options.count=8192] Particle capacity (16–131072). * @param {number[]|number} [options.lifetime=[0.8,2.0]] Lifetime range (seconds). * @param {('rate'|'burst')} [options.emission='rate'] Continuous staggered emission or looping burst. * @param {number|null} [options.rate=null] Particles/second for 'rate' (default count / mean lifetime). * @param {Object} [options.spawnShape] `{ type: 'point'|'sphere'|'box', center, radius, extents }`. * @param {Object} [options.initialVelocity] `{ direction, speed: [min,max], spread: 0..1 }` cone. * @param {number[]} [options.gravity=[0,-9.8,0]] World-space acceleration. * @param {number} [options.drag=0.05] Per-second velocity damping (0..1). * @param {number} [options.curlStrength=0] Built-in curl-noise force magnitude. * @param {number} [options.curlScale=0.05] Curl-noise spatial frequency. * @param {number[]} [options.sizeRange=[0.5,1.5]] Per-particle world size range. * @param {number} [options.particleRadius=0.5] Collision radius for interaction coupling. * @param {number|null} [options.fixedTimeStep=1/60] Fixed substep (seconds); null = variable. * @param {number} [options.maxSubsteps=3] Max substeps per frame. * @param {number} [options.maxFrameDelta=0.1] Frame delta clamp (seconds). * @param {Object|null} [options.spawnSource=null] `{ count, samplePosition( indexNode ) }` surface samples. * @param {Object} [options.hooks] `{ spawnPosition(ctx), spawnVelocity(ctx), force(ctx) }` TSL factories. */ constructor(options?: ParticlesOptions); /** * Kernels build lazily so hosts can attach `spawnSource` / `hooks` after * construction (device-free) and before the first step. */ private _ensureKernels; /** Per-particle TSL context handed to authored hooks. */ private _hookContext; /** Built-in spawn position from the configured shape (or external source). */ private _builtinSpawnPosition; /** Built-in spawn velocity: a cone around the configured direction. */ private _builtinSpawnVelocity; private _buildKernels; private _spawnPositionNode; private _spawnVelocityNode; /** * Couple this system to a shared interaction world (consumer side of the * interaction bus). Mirrors `Boids.setInteractionWorld`. * * @param {GPUInteractionWorld} interactionWorld Initialized-or-not world. * @param {Object} [options] Collision coupling options (layer/mask/restitution…). * @returns {Particles} this */ setInteractionWorld(interactionWorld: GPUInteractionWorld, options?: SimulationInteractionOptions): this; /** @returns {Particles} this */ clearInteractionWorld(): this; /** * Capability report for the coupled interaction world (consumer contract). * * @param {THREE.WebGPURenderer} [renderer] * @returns {Object} `{ available, ... }` — `available: null` without a renderer. */ getInteractionCapabilityReport(renderer?: Renderer | null): ParticlesInteractionCapabilityReport; private _resolveStepConfig; /** * Advance the system. Mirrors `Boids.step`: an injected * `externalDeltaSeconds` bypasses wall-clock timing entirely so hosts and * harnesses can step deterministically. * * @param {THREE.WebGPURenderer} renderer * @param {number|null} [externalDeltaSeconds=null] * @returns {Promise} */ step(renderer: Renderer, externalDeltaSeconds?: number | null): Promise; dispose(): void; }