import * as THREE from 'three/webgpu'; import type { Renderer } from 'three/webgpu'; import type { SpatialLookupHelpers } from './compute/helpers.js'; import type { SpatialGridBuffers, SpatialGridOptions, SpatialGridProfile, SpatialGridUniforms } from './SpatialGrid.js'; import type { ParticleInteractionWorld, SimulationInteractionOptions } from '../Interaction/GPUInteractionSimulation.js'; import type { BVHVolumeConstraint } from '../BVHVolumeConstraint.js'; import type { SDFVolumeConstraint } from '../SDFVolumeConstraint.js'; import type { TSLFunction, TSLMat4Node, TSLStorageNode, TSLUniformNode } from '../../types/tsl.js'; export type BoidsVectorStorage = TSLStorageNode<'vec2'> | TSLStorageNode<'vec3'>; export interface BoidsDebugBuffers { activated: TSLStorageNode<'uint'>; cellId: TSLStorageNode<'uint'>; } export interface BoidsBuffers { positions: BoidsVectorStorage; velocities: BoidsVectorStorage; phase: TSLStorageNode<'float'>; prevVelocities?: BoidsVectorStorage; directions?: BoidsVectorStorage; instanceMatrices: TSLStorageNode<'mat4'> | null; debug?: BoidsDebugBuffers; } export interface BoidsUniforms { now: TSLUniformNode<'float', number>; deltaTime: TSLUniformNode<'float', number>; timeScale: TSLUniformNode<'float', number>; domainDimensions: TSLUniformNode<'vec3', THREE.Vector3>; speedLimit: TSLUniformNode<'float', number>; integrationSpeed: TSLUniformNode<'float', number>; separation: TSLUniformNode<'float', number>; alignment: TSLUniformNode<'float', number>; cohesion: TSLUniformNode<'float', number>; rayOrigin: TSLUniformNode<'vec3', THREE.Vector3>; rayDirection: TSLUniformNode<'vec3', THREE.Vector3>; zoneRadius: TSLUniformNode<'float', number>; centerPull: TSLUniformNode<'float', number>; pointerRadius: TSLUniformNode<'float', number>; pointerStrength: TSLUniformNode<'float', number>; noiseStrength: TSLUniformNode<'float', number>; noiseScale: TSLUniformNode<'float', number>; noiseTimeScale: TSLUniformNode<'float', number>; randomSeed: TSLUniformNode<'vec2', THREE.Vector2>; particleCount: TSLUniformNode<'uint', number>; } export interface BoidsSpatialGrid { buffers: SpatialGridBuffers; ubos: SpatialGridUniforms; positionToCellCoords: SpatialLookupHelpers['positionToCellCoords']; cellKeyToHash: SpatialLookupHelpers['cellKeyToHash']; gridCellSize: THREE.Vector3; gridCellCount: number; lookupAlgorithm: 'global' | 'workgroup'; lookupWorkgroupSize: number; setInput(positions: BoidsVectorStorage, particleCount: number): void; updateKernelRadius(radius: number, domain: THREE.Vector3): boolean; computeGrid(renderer: Renderer): boolean; getProfile(): SpatialGridProfile; readProfile(renderer: Renderer): Promise; resetProfileCounters(renderer: Renderer): void; dispose(): void; } export type BoidsInitialPositions = THREE.StorageBufferAttribute | THREE.StorageInstancedBufferAttribute; export interface BoidsOptions { count?: number | undefined; is3D?: boolean | undefined; domainDimensions?: THREE.Vector3 | undefined; speedLimit?: number | undefined; separation?: number | undefined; alignment?: number | undefined; cohesion?: number | undefined; useRelativeParameters?: boolean | undefined; debug?: boolean | undefined; useDirection?: boolean | undefined; useMatrices?: boolean | undefined; useSpatialGrid?: boolean | undefined; timeScale?: number | undefined; fixedTimeStep?: number | null | undefined; maxSubsteps?: number | undefined; maxFrameDelta?: number | undefined; spatialGridOptions?: SpatialGridOptions | null | undefined; spatialGrid?: BoidsSpatialGrid | null | undefined; sdfVolumeConstraint?: SDFVolumeConstraint | null | undefined; bvhVolumeConstraint?: BVHVolumeConstraint | null | undefined; initialPositions?: BoidsInitialPositions | null | undefined; interactionWorld?: ParticleInteractionWorld | null | undefined; randomSeed?: readonly [number, number] | null | undefined; interaction?: SimulationInteractionOptions | undefined; } /** * @typedef {Object} BoidsBuffers * @memberof Boids * @property {THREE.InstancedBufferAttribute} positions Boid world positions (vec3). * @property {THREE.InstancedBufferAttribute} velocities Boid velocities (vec3). * @property {THREE.InstancedBufferAttribute} phase Flapping animation phase (float). * @property {THREE.InstancedBufferAttribute} [prevVelocities] Previous frame velocities (optional, if `useDirection` enabled). * @property {THREE.InstancedBufferAttribute} [directions] Smoothed directions (optional, if `useDirection` enabled). */ /** * GPU-accelerated boids flocking simulation with spatial grid optimization. * * **Features** * - Classic boids rules: separation, alignment, cohesion with configurable weights * - Spatial grid acceleration (default) or naive O(N²) neighbor search * - 2D and 3D modes with boundary reflection * - Pointer-based interaction for user-driven steering * - Optional instanced mesh with automatic orientation from velocities * - Phase tracking for wing flapping animation sync * - Supports initialization from external samplers (e.g., ComputeBVHSampler) * - GPU culling support with custom instanceMatrix (Three.js r182+) * * **Architecture** * - Compute passes: GPU init → velocity (neighbor influence) → position (integration) * - Spatial grid reduces neighbor search from O(N²) to O(N) via cell hashing * - Three behavioral zones: separation (repel), alignment (match velocity), cohesion (attract) * - Node-based instancing helper provides per-boid transform matrices for rendering * * ```js * import { Boids } from 'three-blocks'; * import { ConeGeometry, InstancedMesh, MeshStandardNodeMaterial } from 'three/webgpu'; * import { instanceIndex } from 'three/tsl'; * * // Basic usage with material positionNode * const boids = new Boids({ * count: 5000, * is3D: true, * domainDimensions: new THREE.Vector3(200, 200, 200), * separation: 0.025, * alignment: 0.03, * cohesion: 0.08, * useRelativeParameters: true, * useMatrices: true * }); * * // Create instanced mesh for rendering * const geometry = new ConeGeometry(0.5, 1.5, 4); * const material = new MeshStandardNodeMaterial(); * material.positionNode = boids.instanceMatrix().element( instanceIndex ); * const mesh = new InstancedMesh(geometry, material, boids.particleCount); * // When useMatrices is enabled, you can feed instance matrices directly * mesh.instanceMatrix = boids.buffers.instanceMatrices.value; * scene.add(mesh); * * // Simulation loop * function animate() { * boids.step(renderer); * renderer.render(scene, camera); * requestAnimationFrame(animate); * } * ``` * * ```js * import { Boids, ComputeBVHSampler, ComputeInstanceCulling } from 'three-blocks'; * import { instanceCullingIndex } from 'three-blocks/instance-culling'; * import { ConeGeometry, InstancedMesh, MeshPhysicalNodeMaterial } from 'three/webgpu'; * * // Advanced: Initialize from ComputeBVHSampler with GPU culling (Three.js r182+) * const sampler = new ComputeBVHSampler(sdfGenerator, renderer, count); * await sampler.compute(); * * const boids = new Boids({ * count, * is3D: true, * domainDimensions: new THREE.Vector3(100, 100, 100), * separation: 0.035, * alignment: 0.04, * cohesion: 0.03, * useRelativeParameters: true, * useMatrices: true, // Enable instance matrices for GPU culling * initialPositions: sampler.positionsBuffer // Initialize from sampler * }); * * // Create mesh with custom instanceMatrix for GPU culling * const geometry = new ConeGeometry(0.15, 0.6, 4); * const material = new MeshPhysicalNodeMaterial({ color: 0xffffff }); * const mesh = new InstancedMesh(geometry, material, count); * mesh.instanceMatrix = boids.buffers.instanceMatrices.value; * mesh.frustumCulled = true; * scene.add(mesh); * * // Use culling index in material * const instanceCulling = new ComputeInstanceCulling(mesh, renderer); * const culledIndex = instanceCullingIndex(instanceCulling); * material.colorNode = boids.buffers.velocities.element(culledIndex).xyz.length()...; * ``` * * @demo docs/demos/boids.html * @class Boids * @short GPU boids flocking sim with optional spatial grid, 2D/3D modes, and instance-matrix output. * @category Simulation * @tags WebGPU * @see SpatialGrid */ export declare class Boids { is3D: boolean; debug: boolean; useDirection: boolean; useMatrices: boolean; useRelativeParameters: boolean; particleCount: number; particleMaxCount: number; sdfVolumeConstraint: SDFVolumeConstraint | null; bvhVolumeConstraint: BVHVolumeConstraint | null; interactionWorld: ParticleInteractionWorld | null; interaction: Readonly | null; ubos: BoidsUniforms; fixedTimeStep: number | null; maxSubsteps: number; buffers: BoidsBuffers; instanceMatrix: TSLFunction<[], TSLMat4Node>; grid: BoidsSpatialGrid | null; gridCellSize: THREE.Vector3; mesh: THREE.Object3D | null; private _baseSeparation; private _baseAlignment; private _baseCohesion; private _baseSpeedLimit; private _domainAvg; private _maxFrameDelta; private _accumulator; private _prev; private _firstStepPending; private _externalNow; private _copyInitPending; private _computeCopyInit; private _computeInitVelocity; private _gridOwnsInstance; private _gridOptionsUser; private _computeInit; private _computeVelocity; private _computePosition; private _computePostConstraint; private _gpuInitDone; private _interactionKind; private _interactionCompute; private _interactionComputes; private _interactionComputeWorld; private _disposed; /** * @param {Object} [options={}] Configuration options. * @param {number} [options.count=16384] Number of boids to simulate (rounded up to next power of 2). * @param {boolean} [options.is3D=true] Enable 3D mode; false restricts movement to a 2D plane. * @param {THREE.Vector3} [options.domainDimensions] Simulation domain bounds (default: 800x800x800). * @param {number} [options.speedLimit=0.3] Maximum velocity as ratio of domain size when useRelativeParameters=true. * @param {number} [options.separation=0.025] Separation distance as ratio of domain size. * @param {number} [options.alignment=0.03] Alignment distance as ratio of domain size. * @param {number} [options.cohesion=0.08] Cohesion distance as ratio of domain size. * @param {boolean} [options.useRelativeParameters=true] Interpret distances as ratios of domain size. * @param {boolean} [options.debug=false] Enable debug buffers and logging. * @param {boolean} [options.useDirection=false] Allocate buffers for smoothed velocity directions. * @param {boolean} [options.useMatrices=false] Write per-instance transformation matrices to storage buffer. * @param {number} [options.timeScale=1.0] Scales simulation speed. * @param {number|null} [options.fixedTimeStep=1/60] Fixed timestep (seconds). Null for variable dt. * @param {number} [options.maxSubsteps=5] Maximum fixed substeps per frame. * @param {number} [options.maxFrameDelta=0.1] Maximum frame delta to avoid large jumps. * @param {boolean} [options.useSpatialGrid=true] Enable spatial grid acceleration. * @param {Object} [options.spatialGridOptions={}] Options for internal SpatialGrid. * @param {SpatialGrid} [options.spatialGrid=null] Attach a preconfigured SpatialGrid. * @param {THREE.StorageInstancedBufferAttribute} [options.initialPositions=null] External positions buffer. * @param {SDFVolumeConstraint} [options.sdfVolumeConstraint=null] SDF volume boundary constraint. * @param {BVHVolumeConstraint} [options.bvhVolumeConstraint=null] BVH volume boundary constraint. */ constructor(options?: BoidsOptions); /** Attach a shared moving-collider interaction world. */ setInteractionWorld(interactionWorld: ParticleInteractionWorld | null, options?: SimulationInteractionOptions): this; /** Disable shared moving-collider interaction. */ clearInteractionWorld(): this; private _updateGridCellSizeFromRadius; private _syncSpatialGridConfig; private _applySpatialGridInstance; /** * Attach an external `SpatialGrid` instance for neighbor acceleration. * Rebuilds compute passes to use grid-accelerated neighbor lookups. * * @param {SpatialGrid} grid External SpatialGrid instance. * @returns {this} * @throws {Error} If grid is not a valid SpatialGrid instance. */ attachSpatialGrid(grid: BoidsSpatialGrid): this; /** * Create and attach an internal spatial grid for neighbor acceleration. * Automatically configures grid based on current domain and zone radius. * * @param {Object} [options={}] Options forwarded to `SpatialGrid` constructor. * @returns {this} */ enableSpatialGrid(options?: SpatialGridOptions | 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?: SpatialGridOptions | undefined): this; /** * Detach and dispose of the spatial grid, reverting to naive O(N²) neighbor search. * * @returns {this} */ detachSpatialGrid(): this; /** * 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; /** * Synchronize spatial grid configuration with current zone radius and domain. * Automatically called when parameters change. * * @returns {this} */ syncSpatialGrid(): this; private _buildCompute; /** * Advance the simulation by one frame using GPU compute passes. * Executes: grid update (if enabled) → GPU init (first frame) → velocity → position. * * @param {THREE.WebGPURenderer} renderer WebGPU renderer instance. * @param {number|null} [externalDeltaSeconds=null] Optional externally driven frame * delta (seconds). When provided, wall-clock timing is bypassed entirely so * hosts/harnesses can step the simulation deterministically. * @returns {Promise} */ step(renderer: Renderer, externalDeltaSeconds?: number | null): Promise; private _resolveStepConfig; /** * Release GPU storage and compute state owned by this flock. * * An internally created spatial grid is disposed, while an externally attached grid, * renderer, constraints, initial-position source, mesh, geometry, and material remain * caller-owned. Shared interaction state is detached without disposing the interaction * world. The method is idempotent; do not call {@link step} after disposal. */ dispose(): void; }