import * as THREE from 'three/webgpu'; import type { Renderer } from 'three/webgpu'; import type { GUIController, GUIFolder } from '../../Utils/guiUtils.js'; import { SpatialGrid } from '../Boids/SpatialGrid.js'; import type { SpatialGridOptions } from '../Boids/SpatialGrid.js'; import type { ParticleInteractionWorld, SimulationInteractionOptions } from '../Interaction/GPUInteractionSimulation.js'; import { ParticleFluidSimulation } from '../Fluid/ParticleFluidSimulation.js'; import type { FluidComponentsInput } from '../Fluid/FluidInitialization.js'; import type { FluidDomainConfigurationOptions, FluidInitializationConfiguration, FluidMaterialConfiguration, FluidNeighborConfigurationOptions, FluidParticleConfigurationOptions, FluidSolverConfigurationOptions, FluidSpatialGridOptions, FluidTimeStepConfigurationOptions } from '../Fluid/FluidConfiguration.js'; import type { FluidDiagnosticsOptions } from '../Fluid/FluidDiagnostics.js'; import type { FluidCalibrationMode, FluidCalibrationResult } from '../Fluid/FluidCalibration.js'; import type { BVHVolumeConstraint } from '../BVHVolumeConstraint.js'; import type { SDFVolumeConstraint } from '../SDFVolumeConstraint.js'; import type { TSLFunction, TSLMat4Node, TSLStorageNode, TSLUintNode, TSLUniformNode } from '../../types/tsl.js'; export type SPHEquationOfState = 'linear' | 'tait'; export type SPHNegativePressurePolicy = 'allow' | 'clamp'; export type SPHTimeStepPolicy = 'fixed' | 'validated-fixed'; export type SPHInitialPositions = THREE.StorageBufferAttribute | THREE.StorageInstancedBufferAttribute; export type SPHSpatialGridOptions = SpatialGridOptions | FluidSpatialGridOptions; export interface SPHDebugBuffers { activated: TSLStorageNode<'uint'>; cellId: TSLStorageNode<'uint'>; } export interface SPHBuffers { positions: TSLStorageNode<'vec3'>; velocities: TSLStorageNode<'vec3'>; densities: TSLStorageNode<'float'>; pressures: TSLStorageNode<'float'>; totalAcceleration: TSLStorageNode<'vec3'>; densityDeltas?: TSLStorageNode<'float'> | undefined; pressureAcceleration?: TSLStorageNode<'vec3'> | undefined; viscosityAcceleration?: TSLStorageNode<'vec3'> | undefined; pressureForces: TSLStorageNode<'vec3'>; viscosityForces?: TSLStorageNode<'vec3'> | undefined; prevVelocities?: TSLStorageNode<'vec3'> | undefined; directions?: TSLStorageNode<'vec3'> | undefined; instanceMatrices: TSLStorageNode<'mat4'> | null; debug?: SPHDebugBuffers | undefined; } export interface SPHSolverBuffers { pressures: TSLStorageNode<'float'>; totalAcceleration: TSLStorageNode<'vec3'>; pressureAcceleration: TSLStorageNode<'vec3'> | null; viscosityAcceleration: TSLStorageNode<'vec3'> | null; densityDeltas: TSLStorageNode<'float'> | null; } export interface SPHUniforms { now: TSLUniformNode<'float', number>; deltaTime: TSLUniformNode<'float', number>; domainDimensions: TSLUniformNode<'vec3', THREE.Vector3>; domainCenter: TSLUniformNode<'vec3', THREE.Vector3>; domainMatrix: TSLUniformNode<'mat4', THREE.Matrix4>; domainMatrixInverse: TSLUniformNode<'mat4', THREE.Matrix4>; domainScale: TSLUniformNode<'vec3', THREE.Vector3>; domainObjectMatrixInverse: TSLUniformNode<'mat4', THREE.Matrix4>; mass: TSLUniformNode<'float', number>; h: TSLUniformNode<'float', number>; h2: TSLUniformNode<'float', number>; h6: TSLUniformNode<'float', number>; poly6Kernel: TSLUniformNode<'float', number>; spiky: TSLUniformNode<'float', number>; viscosity: TSLUniformNode<'float', number>; restDensity: TSLUniformNode<'float', number>; pressureStiffness: TSLUniformNode<'float', number>; viscosityMu: TSLUniformNode<'float', number>; kinematicViscosity: TSLUniformNode<'float', number>; densityDiffusion: TSLUniformNode<'float', number>; equationOfState: TSLUniformNode<'uint', number>; speedOfSound: TSLUniformNode<'float', number>; gamma: TSLUniformNode<'float', number>; negativePressurePolicy: TSLUniformNode<'uint', number>; pressureFloor: TSLUniformNode<'float', number>; restitution: TSLUniformNode<'float', number>; friction: TSLUniformNode<'float', number>; particleRadius: TSLUniformNode<'float', number>; maxSpeed: TSLUniformNode<'float', number>; gravity: TSLUniformNode<'vec3', THREE.Vector3>; timeScale: TSLUniformNode<'float', number>; randomSeed: TSLUniformNode<'vec2', THREE.Vector2>; particleCount: TSLUniformNode<'uint', number>; rayOrigin: TSLUniformNode<'vec3', THREE.Vector3>; rayDirection: TSLUniformNode<'vec3', THREE.Vector3>; pointerRadius: TSLUniformNode<'float', number>; pointerStrength: TSLUniformNode<'float', number>; pointerEnabled: TSLUniformNode<'float', number>; } export interface SPHDomainBindingOptions { padding?: number | THREE.Vector3 | undefined; autoUpdate?: boolean | undefined; simulationScale?: number | THREE.Vector3 | null | undefined; } export interface SPHOptions { count?: number | undefined; is3D?: boolean | undefined; domainDimensions?: THREE.Vector3 | FluidComponentsInput | undefined; mass?: number | undefined; h?: number | undefined; restDensity?: number | null | undefined; pressureStiffness?: number | undefined; viscosityMu?: number | undefined; restitution?: number | undefined; maxSpeed?: number | undefined; gravity?: THREE.Vector3 | undefined; debug?: boolean | undefined; useDirection?: boolean | undefined; scaleKernelWithDomain?: boolean | undefined; useSpatialGrid?: boolean | undefined; useMatrices?: boolean | undefined; spatialGridOptions?: SPHSpatialGridOptions | null | undefined; spatialGrid?: SpatialGrid | null | undefined; sdfVolumeConstraint?: SDFVolumeConstraint | null | undefined; bvhVolumeConstraint?: BVHVolumeConstraint | null | undefined; initialPositions?: SPHInitialPositions | null | undefined; scatterZeroInitialPositions?: boolean | undefined; fixedTimeStep?: number | null | undefined; maxSubsteps?: number | undefined; maxFrameDelta?: number | undefined; timeScale?: number | undefined; interactionWorld?: ParticleInteractionWorld | null | undefined; interaction?: SimulationInteractionOptions | undefined; initialization?: FluidInitializationConfiguration | null | undefined; calibration?: Readonly | null | undefined; particleRadius?: number | null | undefined; spacing?: number | null | undefined; calibrationMode?: FluidCalibrationMode | undefined; friction?: number | undefined; equationOfState?: SPHEquationOfState | undefined; speedOfSound?: number | undefined; gamma?: number | undefined; negativePressurePolicy?: SPHNegativePressurePolicy | undefined; pressureFloor?: number | undefined; kinematicViscosity?: number | null | undefined; timeStepPolicy?: SPHTimeStepPolicy | undefined; boundaryDensitySupport?: boolean | undefined; densityDiffusion?: number | undefined; particles?: FluidParticleConfigurationOptions | null | undefined; material?: FluidMaterialConfiguration | null | undefined; timeStep?: FluidTimeStepConfigurationOptions | null | undefined; domain?: FluidDomainConfigurationOptions | null | undefined; neighbors?: FluidNeighborConfigurationOptions | null | undefined; solverOptions?: FluidSolverConfigurationOptions | null | undefined; diagnostics?: FluidDiagnosticsOptions | null | undefined; readonly [name: string]: unknown; } export interface SPHBoundaryContext extends Record { phase: 'pre-pressure' | 'post-integrate'; deltaTime: number; } export interface SPHRendererLimits { readonly maxStorageBuffersPerShaderStage: 8 | 10; } export interface SPHGUIController extends GUIController { name(label: string): this; } export interface SPHGUIFolder extends GUIFolder { addFolder(name: string): SPHGUIFolder; add(object: SPH, property: '_baseKernelRadius', minimum?: number, maximum?: number, step?: number): SPHGUIController; add(object: TObject, property: TKey, minimum?: number, maximum?: number, step?: number): SPHGUIController; close(): unknown; } export interface SPHGUI { addFolder(name: string): SPHGUIFolder; } export interface SPHGUIFolders { main: SPHGUIFolder; physics: SPHGUIFolder; simulation: SPHGUIFolder; domain: SPHGUIFolder; pointer: SPHGUIFolder; } /** * @typedef {Object} SPHBuffers * @memberof SPH * @property {THREE.InstancedBufferAttribute} positions Particle world positions (vec3). * @property {THREE.InstancedBufferAttribute} velocities Particle velocities (vec3). * @property {THREE.InstancedBufferAttribute} densities Computed particle densities (float). * @property {THREE.InstancedBufferAttribute} pressures Computed particle pressures (float). * @property {THREE.InstancedBufferAttribute} totalAcceleration Combined pressure and viscosity acceleration (vec3). * @property {THREE.InstancedBufferAttribute} pressureForces Read-only alias for `totalAcceleration`. * @property {THREE.InstancedBufferAttribute} [pressureAcceleration] Pressure acceleration diagnostics (optional). * @property {THREE.InstancedBufferAttribute} [viscosityAcceleration] Viscosity acceleration diagnostics (optional). * @property {THREE.InstancedBufferAttribute} [viscosityForces] Read-only alias for `viscosityAcceleration` when enabled. * @property {THREE.InstancedBufferAttribute} [densityDeltas] Optional density-diffusion scratch buffer. * @property {THREE.InstancedBufferAttribute} [prevVelocities] Previous frame velocities (optional, if `useDirection` enabled). * @property {THREE.InstancedBufferAttribute} [directions] Smoothed directions (optional, if `useDirection` enabled). */ /** * Smoothed Particle Hydrodynamics (SPH) fluid simulation with spatial grid acceleration. * * **Features** * - Pressure, viscosity, and gravity forces with configurable SPH kernels (Poly6, Spiky, Viscosity) * - Spatial grid acceleration (default) or naive O(N²) neighbor search * - 2D and 3D modes with appropriate kernel functions * - Domain attachment to Three.js objects for dynamic boundary transforms * - Pointer-based interaction for user-driven forces * - Optional direction storage for oriented particle rendering * * **Architecture** * - Compute passes: density → pressure → forces (pressure + viscosity) → interaction → integration * - Spatial grid reduces neighbor search from O(N²) to O(N) via cell hashing * - Domain matrix transforms allow particles to follow moving/rotating objects * - Kernel radius auto-scales with domain transformations when `scaleKernelWithDomain` is enabled * * ```js * import { SPH, sphereImpostorPosition } from 'three-blocks'; * import { TriangleGeometry } from '../helpers/exampleGeometries.js'; * import { sphereImpostorAlpha, sphereImpostorNormal, sphereImpostorShadow } from 'three-blocks/sphere-impostors'; * import { Mesh, MeshStandardNodeMaterial } from 'three/webgpu'; * import { instanceIndex } from 'three/tsl'; * * const sph = new SPH({ * count: 2000, * is3D: true, * domainDimensions: new THREE.Vector3(20, 20, 20), * h: 1.2, * viscosityMu: 0.15, * useMatrices: false * }); * * // One camera-facing triangle per particle; no instance matrices. * const radius = 0.3; * const material = new MeshStandardNodeMaterial(); * material.positionNode = sphereImpostorPosition({ * position: sph.buffers.positions.element(instanceIndex), radius, * }); * material.normalNode = sphereImpostorNormal(); * material.opacityNode = sphereImpostorAlpha(); * material.alphaTest = 0.5; * material.castShadowNode = sphereImpostorShadow(); * const mesh = new Mesh(new TriangleGeometry(), material); * mesh.count = sph.particleCount; * mesh.frustumCulled = false; * mesh.raycast = () => {}; * scene.add(mesh); * * // Simulation loop * async function animate() { * await sph.step(renderer); * renderer.render(scene, camera); * requestAnimationFrame(animate); * } * ``` * * @class SPH * @short GPU SPH fluid solver with spatial grid acceleration, domain binding, and optional oriented particles. * @category Simulation * @tags WebGPU * @demo docs/demos/sph.html * @see SpatialGrid */ export declare class SPH extends ParticleFluidSimulation { ubos: SPHUniforms; buffers: SPHBuffers; grid: SpatialGrid | null; _domainSize: THREE.Vector3; _positionsArray: Float32Array | undefined; _velocitiesArray: Float32Array | undefined; _prev: number | null; debug: boolean; useDirection: boolean; scaleKernelWithDomain: boolean; useMatrices: boolean; timeStepPolicy: SPHTimeStepPolicy; boundaryDensitySupport: boolean; densityDiffusion: number; particleMaxCount: number; friction: number; sdfVolumeConstraint: SDFVolumeConstraint | null; bvhVolumeConstraint: BVHVolumeConstraint | null; interactionWorld: ParticleInteractionWorld | null; interaction: Readonly | null; solverBuffers: SPHSolverBuffers; instanceMatrix: TSLFunction<[], TSLMat4Node>; instanceMatrixNode: (indexNode?: TSLUintNode) => TSLMat4Node; mesh: THREE.Object3D | null; gridCellSize: THREE.Vector3; domainObject: THREE.Object3D | null; folder: SPHGUIFolder | null | undefined; private _baseKernelRadius; private _autoParticleSpacing; private _particleSpacing; private _calibrationMode; private _autoParticleRadius; private _writeAccelerationComponents; private _autoRestDensity; private _domainMatrix; private _domainMatrixInverse; private _domainScale; private _domainScaleMax; private _domainCenterLocal; private _domainBounds; private _domainBoundsBase; private _objectMatrixInverse; private _objectMatrix; private _domainPadding; private _domainAutoUpdate; private _scratch; private _gridLastDomainDimensions; private _gridLastKernelRadius; private _copyInitPending; private _computeCopyInit; private _prevVelocitiesArray; private _directionsArray; private _gridOwnsInstance; private _gridOptionsUser; private _interactionUsesParticleRadius; private _interactionKind; private _interactionCompute; private _interactionComputes; private _interactionComputeWorld; private _computeDensity; private _computePressure; private _computeDensityDiffusionDelta; private _applyDensityDiffusion; private _computeForces; private _computeInteraction; private _computeIntegrate; /** * @param {Object} [options={}] Configuration options. * @param {number} [options.count=5000] Number of particles to simulate. * @param {boolean} [options.is3D=true] Enable 3D mode; false uses 2D kernels. * @param {THREE.Vector3} [options.domainDimensions] Simulation domain size (default: 32x32x32). * @param {number} [options.mass=0.4] Particle mass. * @param {number} [options.h=1.0] Smoothing kernel radius. * @param {number|null} [options.restDensity=null] Target rest density; null derives a populated-lattice rest state. * @param {number|null} [options.particleRadius=null] Boundary offset radius; null derives approximately `0.48 * spacing`. * @param {number|null} [options.spacing=null] Nominal rest spacing; null derives `0.5 * h`. * @param {string} [options.calibrationMode='discrete-lattice'] Automatic rest-density mode; `self-kernel` uses an isolated-particle reference. * @param {number} [options.pressureStiffness=100.0] Pressure force multiplier. * @param {number} [options.viscosityMu=0.12] Viscosity coefficient. * @param {number} [options.restitution=0.1] Boundary collision restitution. * @param {number} [options.maxSpeed=15.0] Maximum particle velocity. * @param {THREE.Vector3} [options.gravity] Gravity acceleration (default: -9.81 Y). * @param {number|null} [options.fixedTimeStep=1/60] Fixed timestep (null for variable dt). * @param {number} [options.maxSubsteps=5] Max fixed timesteps per frame. * @param {number} [options.maxFrameDelta=0.1] Max frame delta to avoid large jumps. * @param {boolean} [options.debug=false] Enable debug buffers. * @param {boolean} [options.useDirection=false] Allocate direction buffers. * @param {boolean} [options.scaleKernelWithDomain=true] Scale kernel with domain. * @param {boolean} [options.useSpatialGrid=true] Enable spatial grid acceleration. * @param {boolean} [options.useMatrices=false] Write per-instance matrices. * @param {Object} [options.spatialGridOptions={}] SpatialGrid options. * @param {SpatialGrid} [options.spatialGrid=null] Preconfigured SpatialGrid. * @param {THREE.StorageInstancedBufferAttribute} [options.initialPositions=null] External positions buffer. * @param {boolean} [options.scatterZeroInitialPositions=false] Replace zero-valued sampled positions with deterministic in-domain positions. * @param {SDFVolumeConstraint} [options.sdfVolumeConstraint=null] SDF boundary constraint. * @param {BVHVolumeConstraint} [options.bvhVolumeConstraint=null] BVH boundary constraint. * @param {boolean} [options.boundaryDensitySupport=true] Add mirrored density support for curved boundary constraints. * @param {number} [options.densityDiffusion=0] Optional dimensionless pre-pressure density smoothing coefficient. * @param {Object} [options.diagnostics=null] Optional diagnostic allocation settings. * @param {boolean} [options.diagnostics.accelerationComponents=false] Allocate and write split pressure/viscosity acceleration buffers. */ constructor(options?: SPHOptions); /** Attach a shared moving-collider interaction world. */ setInteractionWorld(interactionWorld: ParticleInteractionWorld | null, options?: SimulationInteractionOptions): this; /** Disable shared moving-collider interaction. */ clearInteractionWorld(): this; private _applyDomainScaleToBounds; private _reprojectParticles; private _buildCompute; private _updateKernelForScale; private _setDomainScaleValue; /** Report renderer limits required by the active optional SPH diagnostics. */ getRequiredRendererLimits(): SPHRendererLimits; /** * Manually set the simulation domain dimensions. * Updates UBOs and synchronizes the spatial grid. * * @param {THREE.Vector3} dimensions New domain dimensions. * @returns {this} */ setDomainDimensions(dimensions: THREE.Vector3): this; private _syncGridDomain; /** * Bind simulation domain to a Three.js object's world-space bounds. * Particles will be constrained to the object's local space and follow its transforms. * * @param {THREE.Object3D} object Target object (mesh or group). * @param {Object} [options={}] Configuration options. * @param {number|THREE.Vector3} [options.padding=0] Extend domain bounds by padding. * @param {boolean} [options.autoUpdate=true] Update domain matrices every frame. * @param {number|THREE.Vector3} [options.simulationScale=null] Override domain scale for visualization. * @returns {this} */ setDomainFromObject(object: THREE.Object3D, { padding, autoUpdate, simulationScale }?: SPHDomainBindingOptions): this; private _updateDomainBoundsFromObject; private _updateDomainMatricesFromObject; private _applySpatialGridInstance; /** * Attach an external `SpatialGrid` instance for neighbor acceleration. * Rebuilds compute passes to use grid-accelerated lookups. * * @param {SpatialGrid} grid External SpatialGrid instance. * @returns {this} * @throws {Error} If grid is not a valid SpatialGrid instance. */ attachSpatialGrid(grid: SpatialGrid): this; /** * Update the SPH smoothing kernel radius (h parameter). * Recomputes kernel coefficients and syncs spatial grid cell size. * * @param {number} radius New smoothing radius (h). * @returns {this} */ setSmoothingRadius(radius: number): this; /** * Set rest density manually. Passing null/undefined re-enables auto rest density. * @param {number|null|undefined} value * @returns {this} */ setRestDensity(value: number | null | undefined): this; /** * Set particle mass. Recomputes auto rest density when enabled. * @param {number} value * @returns {this} */ setMass(value: number): this; /** * Enable/disable automatic kernel radius scaling with domain transformations. * * @param {boolean} enabled Whether to scale h with domain scale. * @returns {this} */ setKernelScaleEnabled(enabled: boolean): this; /** * Create and attach an internal spatial grid for neighbor acceleration. * Automatically configures grid based on current domain and kernel radius. * * @param {Object} [options={}] Options forwarded to `SpatialGrid` constructor. * @returns {this} */ enableSpatialGrid(options?: SPHSpatialGridOptions | null): this; /** * Toggle spatial grid acceleration. * * @param {boolean} enabled Enable (true) or disable (false) spatial grid. * @param {Object} [options] Options passed to `enableSpatialGrid()` if enabling. * @returns {this} */ setSpatialGridEnabled(enabled: boolean, options?: SPHSpatialGridOptions | null | undefined): this; /** * Detach and dispose of the spatial grid, reverting to naive O(N²) neighbor search. * * @returns {this} */ detachSpatialGrid(): this; /** * Synchronize spatial grid configuration with current domain and kernel radius. * Automatically called when domain or kernel changes. * * @returns {this} */ syncSpatialGrid(): this; /** * Advance the simulation by one frame using GPU compute passes. * Executes: grid update → density → pressure → forces → interaction → integration. * * @param {THREE.WebGPURenderer} renderer WebGPU renderer instance. * @returns {Promise} */ step(renderer: Renderer, deltaTime?: number | undefined): Promise; private _hasExternalBoundaryDensitySupport; private _contributeExternalBoundaryDensity; private _resolveStepConfig; dispose(): void; /** * Attach SPH parameters to a GUI for interactive tuning. * Compatible with lil-gui, dat.gui, and Three.js Inspector. * * @param {Object} gui A lil-gui instance or folder. * @param {Object} [options={}] Configuration options. * @param {string} [options.folderName='SPH'] Name for the main folder. * @param {boolean} [options.open=false] Whether folders start open. * @returns {Object} Object containing created GUI folders: `{ main, physics, simulation, domain, pointer }`. * * @example * const gui = new GUI( { title: 'Simulation' } ); * const sph = new SPH({ count: 1000 }); * const folders = sph.attachGUI(gui, { open: true }); * folders.physics.add(sph.ubos.mass, 'value', 0.1, 2).name('Custom Mass'); */ attachGUI(gui: SPHGUI, { folderName, open }?: { folderName?: string | undefined; open?: boolean | undefined; }): SPHGUIFolders; /** * Detach the SPH parameters GUI. */ disposeGUI(): void; }