import * as THREE from 'three/webgpu'; import type { ComputeNode, Mesh, Renderer, StorageBufferAttribute, StorageInstancedBufferAttribute } from 'three/webgpu'; import type { TSLStorageNode, TSLUniformNode } from '../types/tsl.js'; export interface ComputeMeshDynamicSurfaceSamplerOptions { renderer: Renderer; count: number; seed?: number | undefined; useVertexNormals?: boolean | undefined; } export interface ComputeMeshDynamicSurfaceSamplerComputeOptions { resampleIndex?: number | undefined; } export interface ComputeMeshDynamicSurfaceSamplerOutputs { position: StorageBufferAttribute; normal: StorageBufferAttribute; matrix: StorageInstancedBufferAttribute; } export interface ComputeMeshDynamicSurfaceSamplerReadback { positions: Float32Array; normals: Float32Array; } /** * Dynamic, center-biased GPU mesh surface sampler using TSL. * * **Overview** * This class provides a specialized sampling strategy that concentrates points around a moving center in world space. * It is ideal for effects like: * - **Footstep Dust**: Spawning particles only near the character's feet. * - **Impact Debris**: Concentrating geometry at collision points. * - **Local Detail**: Adding high-density details (grass, pebbles) only where the camera is looking. * * **Mechanism** * 1. **Candidate Search**: Per-frame, it compacts triangles intersecting the target sphere. * 2. **Blue Noise Sampling**: Samples points on those triangles using stable blue noise. * 3. **Hysteresis**: Retains existing samples inside a slightly larger radius to prevent popping. * * Candidate order is deliberately left compacted rather than sorted. Existing instances retain * their face ID, so sorting every source triangle did not improve temporal stability but added an * O(n log²n) pass to every frame. * * **Example: Footstep dust particles** * * ```js * import { ComputeMeshDynamicSurfaceSampler } from 'three-blocks'; * import * as THREE from 'three/webgpu'; * * const sampler = new ComputeMeshDynamicSurfaceSampler(terrain, 'position', { * renderer, * count: 2048, * seed: 42 * }); * * const dustGeometry = new THREE.PlaneGeometry(0.1, 0.1); * const dustMaterial = new THREE.SpriteNodeMaterial({ transparent: true }); * const dustMesh = new THREE.InstancedMesh(dustGeometry, dustMaterial, 2048); * dustMesh.instanceMatrix = sampler.outputs.matrix; * scene.add(dustMesh); * * function animate() { * sampler.setCenter(character.position.x, character.position.y, character.position.z); * sampler.setRadius(3.0); * sampler.setObjectMatrix(terrain.matrixWorld); * sampler.compute(); * } * ``` * * **Example: Dynamic geometry (GPU-animated mesh)** * * ```js * const sampler = new ComputeMeshDynamicSurfaceSampler(animatedMesh, 'position', { * renderer, * count: 1024 * }); * sampler.setDynamicGeometry(true); * sampler.uDensity.value = 2.0; * sampler.uAreaPow.value = 0.5; * sampler.uHysteresis.value = 2.0; * ``` * * @class ComputeMeshDynamicSurfaceSampler * @category Compute * @tags WebGPU * @demo docs/demos/dynamic-surface-sampler.html * @private */ export declare class ComputeMeshDynamicSurfaceSampler { renderer: Renderer | null; count: number; _disposed: boolean; uResampleIndex: TSLUniformNode<'int', number>; uSeed: TSLUniformNode<'float', number>; uCenter: TSLUniformNode<'vec3', THREE.Vector3>; uRadius: TSLUniformNode<'float', number>; uObjectMatrix: TSLUniformNode<'mat4', THREE.Matrix4>; uObjectNormalMatrix: TSLUniformNode<'mat3', THREE.Matrix3>; uObjectMaxScale: TSLUniformNode<'float', number>; uFalloffExp: TSLUniformNode<'float', number>; uHysteresis: TSLUniformNode<'float', number>; uMaxArea: TSLUniformNode<'float', number>; uAreaPow: TSLUniformNode<'float', number>; uDensity: TSLUniformNode<'float', number>; uMicroDensity: TSLUniformNode<'float', number>; uTrackSurvivors: TSLUniformNode<'uint', number>; TRI_COUNT: number; useVertexNormals: boolean; indexSSBO: StorageBufferAttribute; _ownsPositionSSBO: boolean; positionSSBO: StorageBufferAttribute; POS_COUNT: number; normalSSBO: StorageBufferAttribute | null; _ownsNormalSSBO: boolean; faceDataSSBO: StorageBufferAttribute; faceAreaSSBO: StorageBufferAttribute; counterSSBO: StorageBufferAttribute; outPosSSBO: StorageBufferAttribute; outNrmSSBO: StorageBufferAttribute; outMatSSBO: StorageInstancedBufferAttribute; candidatePairs: TSLStorageNode<'uvec2'>; clearCounters: ComputeNode; rebuildFaceData: ComputeNode; buildCandidates: ComputeNode; rebuildAndBuildCandidates: ComputeNode; sampleFromCandidates: ComputeNode; _geometryDirty: boolean; _dynamicGeometry: boolean; _compiled: boolean; /** * Create a new dynamic surface sampler. * * @param {THREE.Mesh} mesh - Source mesh to sample from. * @param {string} [attributeName='position'] - Name of the position attribute to use. * @param {Object} options - Configuration options. * @param {THREE.WebGPURenderer} options.renderer - WebGPU renderer instance. * @param {number} options.count - Maximum number of samples to generate. * @param {number} [options.seed=1337] - Random seed for stable sampling. * @param {boolean} [options.useVertexNormals=true] - Interpolate vertex normals when available. */ constructor(mesh: Mesh, attributeName: string | undefined, options: ComputeMeshDynamicSurfaceSamplerOptions); /** Center position in world space. */ setCenter(x: number, y: number, z: number): this; /** Radius in world units. */ setRadius(radius: number): this; /** World matrix for the source mesh. */ setObjectMatrix(matrix: THREE.Matrix4): this; get outputs(): ComputeMeshDynamicSurfaceSamplerOutputs; /** * Recompute around the current center and radius. * * The full update is submitted in one command batch: clear counters, compact candidates, * then sample. Dynamic geometry rebuilds its face data in the candidate pass. * * @param {Object} [options] * @param {number} [options.resampleIndex] - Update only one output instance. */ compute(options?: ComputeMeshDynamicSurfaceSamplerComputeOptions): void; /** Read sampled world-space positions and normals. */ readback(): Promise; /** @returns {Promise} */ readSurvivorCountAsync(): Promise; /** Force a rebuild of face centers, radii, and areas from current positions. */ rebuildFaceDataAsync(): Promise; /** Mark CPU-updated position and normal attributes for upload on the next compute. */ markGeometryDirty(): this; /** Enable per-frame face-data rebuild for dynamic geometry updated on the GPU. */ setDynamicGeometry(enabled?: boolean): this; /** Enable survivor-count atomics. Disable when diagnostics are not being read. */ setTrackSurvivors(enabled?: boolean): this; /** Release compute pipelines and owned storage buffers. */ dispose(): void; }