import { Mesh, NodeMaterial, StorageTexture, Vector4 } from 'three/webgpu'; import type { ComputeNode, Renderer } from 'three/webgpu'; import type { TSLFloatNode, TSLMat4Node, TSLStorageNode, TSLTextureNode, TSLUniformArrayNode, TSLUniformNode, TSLVec2Node } from '../../types/tsl.js'; import { RangedReadback } from '../../Utils/RangedReadback.js'; import { TriangleGeometry } from '../../Geometries/TriangleGeometry.js'; import type { MPMSolver } from './MPMSolver.js'; export interface SurfaceFieldOptions { isoMass?: number | undefined; foamDecay?: number | undefined; foamGain?: number | undefined; foamSpeedThreshold?: number | undefined; probeCapacity?: number | undefined; texture?: boolean | undefined; foamDeposit?: TSLStorageNode<'uint'> | null | undefined; foamDepositScale?: number | undefined; foamDepositGain?: number | undefined; columnWorldArea?: number | undefined; foamDepositFootprintArea?: number | undefined; } export interface SurfaceFieldUniforms { isoMass: TSLUniformNode<'float', number>; foamDecay: TSLUniformNode<'float', number>; foamGain: TSLUniformNode<'float', number>; foamSpeedThreshold: TSLUniformNode<'float', number>; foamDepositGain: TSLUniformNode<'float', number>; foamCoveragePerDeposit: TSLUniformNode<'float', number>; frameDt: TSLUniformNode<'float', number>; probeCount: TSLUniformNode<'uint', number>; } export interface SurfaceFieldFoamSurface { depthNode: TSLTextureNode; uniforms: { fullResolution: TSLVec2Node; near: TSLFloatNode; far: TSLFloatNode; }; } export interface SurfaceFieldFoamMeshOptions { volumeToWorld?: TSLMat4Node | undefined; surface?: SurfaceFieldFoamSurface | undefined; sceneDepthNode?: TSLTextureNode | null | undefined; splatsPerColumn?: number | undefined; baseSize?: number | undefined; minPixelSize?: number | undefined; maxPixelSize?: number | undefined; displayFraction?: number | undefined; jitter?: number | undefined; coverageThreshold?: number | undefined; coverageFeather?: number | undefined; softDepthDistance?: number | undefined; surfaceBand?: number | undefined; aging?: boolean | undefined; } export interface SurfaceFieldProbeOptions { persistent?: boolean | undefined; unique?: boolean | undefined; } export interface SurfaceFieldProbeWaiter { resolve: (sample: Float32Array) => void; reject: (reason?: unknown) => void; queued: boolean; } export interface SurfaceFieldProbeSlot { x: number; z: number; persistent: boolean; waiters: SurfaceFieldProbeWaiter[]; } export interface SurfaceFieldProbeInputArray extends TSLUniformArrayNode<'vec4'> { array: Vector4[]; } /** * Top-down water surface field over an {@link MPMSolver} grid. * * One compute column per grid XZ cell scans the grid-mass mirror from the top * and stores `vec4( heightNorm, foamCoverage, velocityX, velocityZ )` per column: * an iso-interpolated free-surface height in normalized `[0,1]` domain units, * a bounded foam coverage (whitewater and wake systems stamp deposit counts * into it), and the surface velocity in grid cells/s. Coverage is the Poisson * exposure curve `1 - exp( - depositFootprint / columnArea * deposits )`, so * changing the field resolution or particle lattice does not require retuning * the material threshold. * * A second pass bilinearly samples the field at up to `probeCapacity` * caller-registered XZ probes into a compact result buffer that is read back * asynchronously once per frame — the water-pro buoyancy contract: many * probes, one buffer, one readback. Both passes are returned by * {@link SurfaceField#getPasses} so the owner can append them to the solver's * single per-frame compute submission (`MPMSolver` `postPasses`). * * @short Top-down height/foam/velocity field + batched surface probes for MLS-MPM water. * @category Simulation * @tags WebGPU, TSL, Water, Ocean */ export declare class SurfaceField { solver: MPMSolver; width: number; depth: number; columnCount: number; probeCapacity: number; texture: StorageTexture | null; uniforms: SurfaceFieldUniforms; probeInputs: SurfaceFieldProbeInputArray; fieldBuffer: TSLStorageNode<'vec4'>; maxHeightBuffer: TSLStorageNode<'uint'>; probeResultBuffer: TSLStorageNode<'vec4'>; maxHeightClearPass: ComputeNode | null; fieldPass: ComputeNode | null; probePass: ComputeNode | null; _foamDeposit: TSLStorageNode<'uint'> | null; _foamDepositScale: number; _probeReadback: RangedReadback; _probeValues: Float32Array; _probeSlots: Array; _probeCursor: number; _probeQueueStamp: number; _probeReadbackPending: boolean; _fieldReadback: RangedReadback | null; _renderer: Renderer | null; _foamMeshes: Array>; _disposed: boolean; /** * @param {MPMSolver} solver Solver whose grid mirror is scanned. * @param {Object} [options] * @param {number} [options.isoMass=1] Grid-mass iso level marking the free surface. * @param {number} [options.foamDecay=0.9] Foam exponential decay rate (1/s). * @param {number} [options.foamGain=0.28] Foam stamped per second per grid-unit of surface speed above the threshold. * @param {number} [options.foamSpeedThreshold=9] Surface speed (cells/s) below which no foam is stamped. * @param {number} [options.probeCapacity=128] Maximum simultaneous surface probes. * @param {boolean} [options.texture=false] Also mirror the field into a filterable * rgba16f storage texture (x → u, z → v) for material sampling. * @param {?Node} [options.foamDeposit=null] Atomic per-column uint accumulation buffer * (fixed-point) stamped by whitewater; consumed and cleared here every frame. * @param {number} [options.foamDepositScale=1] Fixed-point decode scale for the deposit buffer. * @param {number} [options.foamDepositGain=1] Foam added per decoded deposit unit. * @param {number} [options.columnWorldArea=1] World-space XZ area represented by one field column. * @param {number} [options.foamDepositFootprintArea=1] World-space area represented by one * whitewater deposit. Coverage exposure per deposit is this value divided by `columnWorldArea`. */ constructor(solver: MPMSolver, { isoMass, foamDecay, foamGain, foamSpeedThreshold, probeCapacity, texture, foamDeposit, foamDepositScale, foamDepositGain, columnWorldArea, foamDepositFootprintArea, }?: SurfaceFieldOptions); /** * Render deposited SurfaceField coverage as small, stable splats in a * water renderer's independent whitewater accumulation pass. Unlike the old * fullscreen value-noise mask, these splats are isotropic in world space, * stay attached to their source columns, and inherit scene/fluid depth * rejection from the target renderer. * * @param {Object} options * @param {Node} options.volumeToWorld Normalized-domain to world matrix uniform. * @param {WaterSurfaceRenderer|WaterRayMarchRenderer} options.surface Water depth owner. * @param {?Node} [options.sceneDepthNode=null] Opaque scene depth texture node. * @param {number} [options.splatsPerColumn=4] Stable sub-cell flecks per field column. * @param {number} [options.baseSize=0.095] World-space splat radius. * @param {number} [options.minPixelSize=1] Minimum display-space diameter. * @param {number} [options.maxPixelSize=14] Maximum display-space diameter. * @param {number} [options.displayFraction=1] Stable stochastic fraction of authored flecks to draw. * @param {number} [options.jitter=0.84] Horizontal sub-cell jitter span; values above one break up the column lattice. * @param {number} [options.coverageThreshold=0.06] Field coverage onset. * @param {number} [options.coverageFeather=0.18] Field coverage transition. * @param {number} [options.softDepthDistance=0.3] View-space depth fade width. * @param {number} [options.surfaceBand=0.06] Allowed offset around the reconstructed surface. * @param {boolean} [options.aging=false] Map decaying coverage to smaller/brighter fresh splats and sparse/lacy old foam. * @returns {Mesh} Fixed-count splat mesh suitable for `setWhitewaterMesh`. */ createFoamMesh({ volumeToWorld, surface, sceneDepthNode, splatsPerColumn, baseSize, minPixelSize, maxPixelSize, displayFraction, jitter, coverageThreshold, coverageFeather, softDepthDistance, surfaceBand, aging, }?: SurfaceFieldFoamMeshOptions): Mesh; _buildPasses(): void; /** * Frame passes to append to the solver submission (field scan + probe * sampling when probes are active). * * @param {number} frameDt Frame delta time in seconds (drives foam decay). * @returns {Array} Compute passes for this frame. */ getPasses(frameDt: number): ComputeNode[]; /** * Register (or reuse) a probe at a normalized domain XZ position. * * @param {number} normX Domain-normalized X in `[0,1]`. * @param {number} normZ Domain-normalized Z in `[0,1]`. * @param {Object} [options] * @param {boolean} [options.persistent=false] Keep the slot until {@link SurfaceField#releaseProbe}. * @param {boolean} [options.unique=false] Always allocate a fresh slot (movable hull points * must not coordinate-dedupe against each other). * @returns {number} Slot index. */ requestProbe(normX: number, normZ: number, { persistent, unique }?: SurfaceFieldProbeOptions): number; /** * Move an existing probe (persistent hull points travel with their body). * * @param {number} slotIndex Slot returned by {@link SurfaceField#requestProbe}. * @param {number} normX Domain-normalized X in `[0,1]`. * @param {number} normZ Domain-normalized Z in `[0,1]`. */ setProbePosition(slotIndex: number, normX: number, normZ: number): void; /** Release a probe slot. */ releaseProbe(slotIndex: number): void; _refreshProbeCount(): void; /** * Latest read-back sample for a slot: `[heightNorm, foam, velX, velZ]`. * Values lag GPU state by the in-flight readback (typically one frame). * * @param {number} slotIndex Slot returned by {@link SurfaceField#requestProbe}. * @param {Float32Array} [target] Optional 4-element destination. * @returns {Float32Array} The sample. */ getProbeSample(slotIndex: number, target?: Float32Array): Float32Array; /** * One-shot probe read: registers a transient probe and resolves with * `[heightNorm, foam, velX, velZ]` after the next completed readback that * includes it. * * @param {number} normX Domain-normalized X in `[0,1]`. * @param {number} normZ Domain-normalized Z in `[0,1]`. * @returns {Promise} The sampled column data. */ readProbe(normX: number, normZ: number): Promise; /** * Queue this frame's asynchronous probe readback. Call after the compute * submission that ran the probe pass. Skipped while a readback is in * flight; waiters are carried to the next one. * * @param {THREE.WebGPURenderer} renderer Active renderer. */ queueProbeReadback(renderer: Renderer): void; /** * One-shot full-field readback for diagnostics and harness gates. * * @param {THREE.WebGPURenderer} renderer Active renderer. * @returns {Promise} `columnCount * 4` floats (height, foam, velX, velZ). */ readField(renderer: Renderer): Promise; dispose(): void; }