import * as THREE from 'three/webgpu'; import type { ComputeNode, Renderer } from 'three/webgpu'; import { TriangleGeometry } from '../../Geometries/TriangleGeometry.js'; import type { MPMMaterialModel } from './MPMFluidModel.js'; import type { MPMSolver, MPMStepPostPassContext } from './MPMSolver.js'; import type { TSLFloatNode, TSLMat4Node, TSLStorageNode, TSLUintNode, TSLUniformNode, TSLVec2Node, TSLVec4Node } from '../../types/tsl.js'; /** Two-value configuration window used by diffuse-particle spawn controls. */ export type DiffuseParticlesRange = readonly [minimum: number, maximum: number]; /** Construction options for {@link DiffuseParticles}. */ export interface DiffuseParticlesOptions { capacity?: number | undefined; kineticEnergyWindow?: DiffuseParticlesRange | undefined; shearWindow?: DiffuseParticlesRange | undefined; crestWindow?: DiffuseParticlesRange | undefined; spawnRate?: number | undefined; trappedAirStrength?: number | undefined; crestStrength?: number | undefined; lifetime?: DiffuseParticlesRange | undefined; sprayDrag?: number | undefined; bubbleBuoyancy?: number | undefined; bubbleDrag?: number | undefined; foamDepositRate?: number | undefined; seed?: number | undefined; workgroupSize?: number | undefined; } /** Frozen MPM solver surface consumed by the diffuse-particle graph. */ export type DiffuseParticlesSolver = Pick; /** Material-model surface observed for optional fluid rest density. */ export type DiffuseParticlesMaterial = MPMMaterialModel; /** Uniforms shared by diffuse simulation and rendering. */ export interface DiffuseParticlesUniforms { dt: TSLUniformNode<'float', number>; frame: TSLUniformNode<'uint', number>; seed: TSLUniformNode<'float', number>; kineticEnergyWindow: TSLUniformNode<'vec2', THREE.Vector2>; shearWindow: TSLUniformNode<'vec2', THREE.Vector2>; crestWindow: TSLUniformNode<'vec2', THREE.Vector2>; spawnRate: TSLUniformNode<'float', number>; trappedAirStrength: TSLUniformNode<'float', number>; crestStrength: TSLUniformNode<'float', number>; lifetimeRange: TSLUniformNode<'vec2', THREE.Vector2>; sprayDrag: TSLUniformNode<'float', number>; bubbleBuoyancy: TSLUniformNode<'float', number>; bubbleDrag: TSLUniformNode<'float', number>; foamDepositRate: TSLUniformNode<'float', number>; parity: TSLUniformNode<'uint', number>; } /** One GPU diffuse-particle struct buffer. */ export type DiffuseParticlesBuffer = TSLStorageNode<'struct'>; /** Ping-pong GPU storage used by survivor compaction. */ export type DiffuseParticlesBuffers = [DiffuseParticlesBuffer, DiffuseParticlesBuffer]; /** Atomic counter/deposit storage used by diffuse passes. */ export type DiffuseParticlesAtomicBuffer = TSLStorageNode<'uint'>; /** Minimal sampleable depth-node contract used for soft intersections. */ export interface DiffuseParticlesDepthNode { sample(uvNode: TSLVec2Node): TSLVec4Node; } /** SSF uniform surface consumed by the whitewater material. */ export interface DiffuseParticlesSurfaceUniforms { fullResolution: TSLVec2Node; near: TSLFloatNode; far: TSLFloatNode; } /** Water surface contract needed by the reduced whitewater path. */ export interface DiffuseParticlesSurface { depthNode: DiffuseParticlesDepthNode; uniforms: DiffuseParticlesSurfaceUniforms; } /** Indirect sprite-mesh construction options. */ export interface DiffuseParticlesMeshOptions { volumeToWorld: TSLMat4Node; baseSize?: number | undefined; minPixelSize?: number | undefined; maxPixelSize?: number | undefined; displayFraction?: number | undefined; surface?: DiffuseParticlesSurface | null | undefined; sceneDepthNode?: DiffuseParticlesDepthNode | null | undefined; softDepthDistance?: number | undefined; surfaceBand?: number | undefined; sunDirection?: THREE.Vector3 | undefined; surfaceField?: THREE.Texture | null | undefined; worldToVolume?: TSLMat4Node | null | undefined; volumeGrid?: THREE.Storage3DTexture | null | undefined; volumeAbsorption?: number | undefined; } /** Ordered post-pass context supplied by {@link MPMSolver}. */ export type DiffuseParticlesPassContext = Pick; /** Ordered prep/update/spawn/args compute tuple. */ export type DiffuseParticlesPasses = [ComputeNode, ComputeNode, ComputeNode, ComputeNode]; /** Indirect-drawn sprite mesh returned by {@link DiffuseParticles.createMesh}. */ export type DiffuseParticlesMesh = THREE.Mesh; /** Struct storage view over the GPU-owned indirect draw arguments. */ export type DiffuseParticlesDrawArgsNode = TSLStorageNode<'struct'> & { get(field: 'instanceCount'): TSLUintNode; }; export declare const DIFFUSE_STATE_SPRAY = 0; export declare const DIFFUSE_STATE_FOAM = 1; export declare const DIFFUSE_STATE_BUBBLE = 2; /** * Whitewater for MLS-MPM water: Ihmsen-style diffuse particles (spray, foam, * bubbles) driven entirely by data the fluid already carries. Spawn * potentials come from the per-particle velocity gradient (`C`) — deviatoric * shear plus a surface-crest term — windowed by kinetic energy; no neighbor * search. Particles classify each frame from the sampled grid mass (spray in * air, foam at the surface, bubbles submerged), advect per type, and expire * by lifetime. Survivors compact into a double buffer with an atomic cursor, * and a one-thread pass writes the indirect draw args — the draw count never * touches the CPU. * * Foam-state particles deposit into the {@link SurfaceField} foam channel * through a fixed-point atomic accumulation buffer, so persistent whitecaps * and wakes emerge where whitewater actually lives. * * Everything is deterministic under a fixed seed: all stochastic terms key * off `hash(index, frame, seed)`. * * @short Spray/foam/bubble whitewater with GPU compaction and readback-free indirect draw. * @category Simulation * @tags WebGPU, TSL, Water, Whitewater */ export declare class DiffuseParticles { solver: DiffuseParticlesSolver; capacity: number; workgroupSize: number; columnCount: number; uniforms: DiffuseParticlesUniforms; buffers: DiffuseParticlesBuffers; countersBuffer: DiffuseParticlesAtomicBuffer; depositBuffer: DiffuseParticlesAtomicBuffer; drawArgs: THREE.IndirectStorageBufferAttribute; drawArgsNode: DiffuseParticlesDrawArgsNode; framePass: ComputeNode; updatePasses: [ComputeNode, ComputeNode]; spawnPasses: [ComputeNode, ComputeNode]; argsPass: ComputeNode; mesh: DiffuseParticlesMesh | null; meshes: DiffuseParticlesMesh[]; private _spawnCount; private _countReadback; private _renderer; private _disposed; /** * @param {MPMSolver} solver Fluid solver supplying particles and the grid mirror. * @param {Object} [options] * @param {number} [options.capacity=131072] Diffuse particle pool size. * @param {Array} [options.kineticEnergyWindow=[15,80]] `|v|²` window (grid units²) gating all spawn. * @param {Array} [options.shearWindow=[4,14]] Deviatoric-shear window (1/s) for the trapped-air term. * @param {Array} [options.crestWindow=[2,7]] Upward-speed window (cells/s) for the crest term. * @param {number} [options.spawnRate=2.2] Spawned particles per second per unit potential. * @param {number} [options.trappedAirStrength=1] Trapped-air potential weight. * @param {number} [options.crestStrength=1.4] Wave-crest potential weight. * @param {Array} [options.lifetime=[0.7,2.2]] Min/max diffuse lifetime (s). * @param {number} [options.sprayDrag=0.35] Quadratic-ish air drag rate for spray (1/s). * @param {number} [options.bubbleBuoyancy=1.6] Bubble upward acceleration as a multiple of |gravity|. * @param {number} [options.bubbleDrag=3] Bubble velocity relaxation toward the fluid (1/s). * @param {number} [options.foamDepositRate=0.9] SurfaceField foam deposited per foam particle per second. * @param {number} [options.seed=1] Deterministic spawn seed. * @param {number} [options.workgroupSize=64] Compute workgroup width. */ constructor(solver: DiffuseParticlesSolver, { capacity, kineticEnergyWindow, shearWindow, crestWindow, spawnRate, trappedAirStrength, crestStrength, lifetime, sprayDrag, bubbleBuoyancy, bubbleDrag, foamDepositRate, seed, workgroupSize, }?: DiffuseParticlesOptions); get depositNode(): TSLStorageNode<'uint'>; get depositScale(): number; private _sampleMirror; private _buildUpdatePass; private _buildSpawnPass; private _buildPasses; /** * Frame passes for the solver submission: prep → compact/advect → spawn → * indirect args. The read/write buffers alternate by frame parity. * * @param {Object} context `{ dt, frame }` from `MPMSolver` postPasses. * @returns {Array} Ordered compute passes. */ getPasses({ dt, frame }: DiffuseParticlesPassContext): DiffuseParticlesPasses; /** * Build the indirect-drawn sprite mesh. Add it to the scene once; its * instance count comes from the GPU args buffer, never the CPU. * * @param {Object} options * @param {Node} options.volumeToWorld Domain matrix uniform (WaterVolume's). * @param {number} [options.baseSize=0.09] Sprite radius (world units) at scale 1. * @param {number} [options.minPixelSize=1.5] Minimum display-space sprite diameter. * @param {number} [options.maxPixelSize=28] Maximum display-space sprite diameter. * @param {number} [options.displayFraction=1] Stable fraction of simulated particles to display. * @param {?WaterSurfaceRenderer} [options.surface=null] SSF owner. When supplied, sprites * output premultiplied density for its reduced whitewater accumulation target. * @param {?Node} [options.sceneDepthNode=null] Scene depth used for soft intersection fade. * @param {number} [options.softDepthDistance=0.3] View-space soft-depth width. * @param {number} [options.surfaceBand=0.08] Surface band separating spray/foam/bubbles. * @param {THREE.Vector3} [options.sunDirection] World-space sparkle direction for spray. * @param {?THREE.Texture} [options.surfaceField=null] Optional SurfaceField used to keep * direct-rendered foam attached to the computed free surface. * @param {?Node} [options.worldToVolume=null] World-to-domain matrix used for horizontal * boundary clipping and SurfaceField lookup. * @returns {THREE.Mesh} Configured sprite mesh. */ createMesh(options: DiffuseParticlesMeshOptions): DiffuseParticlesMesh; /** * Diagnostic alive-count readback (tests only; rendering never reads back). * * @param {THREE.WebGPURenderer} renderer Active renderer. * @returns {Promise} Live diffuse particle count. */ readAliveCount(renderer: Renderer): Promise; dispose(): void; }