import type { BufferGeometry, ComputeNode, Matrix4, Renderer, SkinnedMesh, Storage3DTexture, StorageBufferAttribute, Vector3 } from 'three/webgpu'; import type { TSLFloatInput, TSLFloatNode, TSLStorageNode, TSLUniformNode, TSLVec3Input, TSLVec3Node, TSLVec4Node } from '../types/tsl.cjs'; /** Ordered compute-pass list dispatched as one named batch. */ export type SkinnedMeshSDFComputeBatch = ComputeNode[] & { /** Batch identifier surfaced to development tooling. */ id: string; /** Batch display name surfaced to development tooling. */ name: string; /** Marks the array as a dispatchable compute workload. */ isComputeNode: true; }; /** Construction configuration for {@link SkinnedMeshSDF}. */ export interface SkinnedMeshSDFOptions { /** Cubic voxel dimension of the field; work and memory grow with its cube. */ resolution?: number | undefined; /** World-space size of the axis-aligned field domain. */ domain?: Vector3 | undefined; /** Exact-distance band half-width around the surface, in voxels. */ bandVoxels?: number | undefined; /** Signed distance written where no surface information exists. */ far?: number | undefined; /** Upper clamp applied to per-vertex surface velocity, in world units per second. */ maxSurfaceSpeed?: number | undefined; /** Ping-pong distance-propagation passes; each extends coverage by one voxel. */ propagationPasses?: number | undefined; } /** Per-particle configuration for {@link SkinnedMeshSDF.collide}. */ export interface SkinnedMeshSDFCollideOptions { /** World-space particle position. */ position: TSLVec3Input; /** World-space particle velocity entering the response. */ velocity: TSLVec3Input; /** Particle radius; the response triggers when signed distance drops below it. */ radius?: TSLFloatInput | undefined; /** Fraction of tangential velocity removed on contact; zero slides freely. */ friction?: TSLFloatInput | undefined; /** Fraction of approach speed reflected on contact; zero absorbs the impact. */ restitution?: TSLFloatInput | undefined; /** Separation speed gained per meter of penetration. */ pushOutRate?: TSLFloatInput | undefined; /** Cap on the push-out separation speed, in world units per second. */ maxPushOutSpeed?: TSLFloatInput | undefined; /** * Maximum blend toward a domain-radial escape normal for deep penetrations, * where neighboring seed normals can disagree; zero keeps the raw gradient. */ deepEscape?: TSLFloatInput | undefined; } /** TSL uniform nodes owned and refreshed by a {@link SkinnedMeshSDF} instance. */ export interface SkinnedMeshSDFUniforms { /** Skinned-mesh bind matrix. */ readonly bindMatrix: TSLUniformNode<'mat4', Matrix4>; /** Inverse of the skinned-mesh bind matrix. */ readonly bindMatrixInverse: TSLUniformNode<'mat4', Matrix4>; /** Mesh local-to-world matrix, refreshed by every update. */ readonly objectMatrix: TSLUniformNode<'mat4', Matrix4>; /** Normalized-field to world transform. */ readonly sdfToWorld: TSLUniformNode<'mat4', Matrix4>; /** World to normalized-field transform. */ readonly worldToSdf: TSLUniformNode<'mat4', Matrix4>; /** Timestep used for surface-velocity estimation. */ readonly dt: TSLUniformNode<'float', number>; /** One while velocity history is invalid, zero afterwards. */ readonly firstFrame: TSLUniformNode<'float', number>; /** World-space size of one voxel per axis. */ readonly voxelSize: TSLUniformNode<'vec3', Vector3>; /** Exact-distance band half-width in world units. */ readonly band: TSLUniformNode<'float', number>; /** Signed distance reported where no surface information exists. */ readonly far: TSLUniformNode<'float', number>; } /** * Live signed-distance and surface-velocity field rebuilt from a skinned * mesh's actual triangles, entirely on the GPU, every frame. * * Each {@link SkinnedMeshSDF.dispatch} runs the compute stages: skin (bone * matrices applied to every vertex, with clamped finite-difference surface * velocity), angle-weighted pseudonormals (clear, per-triangle accumulate, * normalize — welded across bind-pose-coincident vertices so glTF seams do * not split them), clear, triangle splat (exact point-to-triangle distances * atomically packed into a narrow band), seed resolve (exact distance plus a * pseudonormal side test — the only place a sign is ever decided), distance * propagation (each cell inherits distance, seeding triangle, AND sign from * its best neighbour, so the in-band decision is transported instead of * re-derived from a half-space test at range), and resolve. The result lands * in {@link SkinnedMeshSDF.texture} as rgba16float — surface velocity in * xyz, signed distance in w — linearly filtered and clamped. * * The texture holds real data all the way into its outermost texels, so a * body touching a domain face (feet on a floor-seated domain) keeps its * boundary layer. Out-of-domain semantics live in the samplers instead: * {@link SkinnedMeshSDF.sample}, {@link SkinnedMeshSDF.distance}, * {@link SkinnedMeshSDF.gradient}, {@link SkinnedMeshSDF.surfaceVelocity}, * and {@link SkinnedMeshSDF.collide} all return `far` empty space outside * the box. Consumers binding {@link SkinnedMeshSDF.texture} directly must * keep their own samples inside the domain (box-clip the ray, or guard the * coordinates) — clamped reads past a face repeat that face's data. * * The skeleton is the provider seam: anything that poses the mesh's bones — * an `AnimationMixer`, MediaPipe or Kinect retargeting, WebXR joints, VRM * mocap — drives the field with no extra configuration. Consumers sample * the field in TSL through {@link SkinnedMeshSDF.sample}, * {@link SkinnedMeshSDF.distance}, {@link SkinnedMeshSDF.gradient}, and * {@link SkinnedMeshSDF.surfaceVelocity}, or bind * {@link SkinnedMeshSDF.texture} and {@link SkinnedMeshSDF.uniforms} * directly. * * Geometry must be indexed, carry `skinIndex`/`skinWeight` attributes, and * stay within 65,535 triangles: triangle ids share one 32-bit atomic word * with the quantized band distance. */ export declare class SkinnedMeshSDF { /** Skinned mesh whose triangles seed the field; borrowed, never mutated. */ readonly mesh: SkinnedMesh; /** Geometry captured from the mesh at construction. */ readonly geometry: BufferGeometry; /** Cubic voxel dimension of the field. */ readonly resolution: number; /** World-space size of the field domain. */ readonly domain: Vector3; /** Signed distance reported where no surface information exists. */ readonly far: number; /** Vertex count read from the mesh geometry. */ readonly vertexCount: number; /** Triangle count read from the mesh geometry index. */ readonly triangleCount: number; /** World-space size of one voxel per axis. */ readonly voxelSize: Vector3; /** Smallest voxel edge; the distance step used during propagation. */ readonly voxelStep: number; /** Exact-distance band half-width in world units. */ readonly band: number; /** Mutable normalized-field to world transform, refreshed by {@link setDomainCenter}. */ readonly sdfToWorldMatrix: Matrix4; /** Mutable world to normalized-field transform, refreshed by {@link setDomainCenter}. */ readonly worldToSdfMatrix: Matrix4; /** Owned rgba16float collider texture: surface velocity xyz, signed distance w. */ readonly texture: Storage3DTexture; /** Owned uniform nodes shared by every pass and external sampler. */ readonly uniforms: SkinnedMeshSDFUniforms; /** Ordered compute batch built once at construction; dispatched per frame. */ readonly passes: SkinnedMeshSDFComputeBatch; /** Skinned world-space vertex positions, one vec4 per vertex. */ readonly positionBuffer: TSLStorageNode<'vec4'>; /** Clamped world-space vertex velocities, one vec4 per vertex. */ readonly velocityBuffer: TSLStorageNode<'vec4'>; /** CPU-readable copy of the resolved field for verification workflows. */ readonly resolvedField: TSLStorageNode<'vec4'>; /** Sample the field at a world position: surface velocity xyz, signed distance w. */ readonly sample: (worldPosition: TSLVec3Input) => TSLVec4Node; /** Signed distance from a world position to the tracked surface. */ readonly distance: (worldPosition: TSLVec3Input) => TSLFloatNode; /** Tetrahedral field gradient at a world position; normalize it for a contact normal. */ readonly gradient: (worldPosition: TSLVec3Input) => TSLVec3Node; /** Interpolated surface velocity of the nearest tracked surface point. */ readonly surfaceVelocity: (worldPosition: TSLVec3Input) => TSLVec3Node; /** * Complete collision response against the tracked surface, for use inside * simulation hooks: sample once, take a tetrahedral contact normal (blended * toward a radial escape for deep penetrations), damp tangential motion by * `friction`, reflect approach speed by `restitution` against the surface's * own velocity, then push out of penetration. Returns the corrected * world-space velocity; positions and velocities are world units — convert * at the call site when the host simulation is normalized. */ readonly collide: (options: SkinnedMeshSDFCollideOptions) => TSLVec3Node; /** Static geometry index uploaded as storage. */ protected readonly indexAttribute: StorageBufferAttribute; /** Bind-pose vertex positions uploaded as storage. */ protected readonly positionAttribute: StorageBufferAttribute; /** Per-vertex bone indices uploaded as storage. */ protected readonly skinIndexAttribute: StorageBufferAttribute; /** Per-vertex bone weights uploaded as storage. */ protected readonly skinWeightAttribute: StorageBufferAttribute; /** Bone matrices re-uploaded by every {@link update}. */ protected readonly boneMatrixAttribute: StorageBufferAttribute; /** Vertex → canonical bind-pose-coincident vertex, for seam-proof pseudonormals. */ protected readonly weldMapAttribute: StorageBufferAttribute; /** Fixed-point angle-weighted normal accumulators, three ints per weld vertex. */ protected readonly pseudoNormalAccumulator: TSLStorageNode<'int'>; /** Normalized per-vertex pseudonormals rebuilt from the posed triangles each frame. */ protected readonly pseudoNormalBuffer: TSLStorageNode<'vec4'>; /** Previous-frame skinned positions used for velocity estimation. */ protected readonly previousPositionBuffer: TSLStorageNode<'vec4'>; /** Packed atomic seed grid written by the splat pass. */ protected readonly seedPack: TSLStorageNode<'uint'>; /** Distance-propagation ping buffer. */ protected readonly fieldA: TSLStorageNode<'vec2'>; /** Distance-propagation pong buffer. */ protected readonly fieldB: TSLStorageNode<'vec2'>; /** Buffer holding the final propagated (distance, triangle) pairs. */ protected readonly finalField: TSLStorageNode<'vec2'>; private readonly _domainCenter; private _renderer; private _firstFrame; private _disposed; /** * Build the complete compute graph for one skinned mesh. * * No GPU work runs until the first {@link dispatch}. * * @param skinnedMesh Indexed, skinned source mesh; borrowed for the instance lifetime. * @param options Field resolution, domain, band, and velocity configuration. */ constructor(skinnedMesh: SkinnedMesh, options?: SkinnedMeshSDFOptions); private _buildGraph; /** * Recenter the field domain around a world-space point. * * The domain is axis-aligned and keeps its configured size; only its * placement moves. Follow a character by passing its hips position with * the y component held at half the domain height so the box stays * grounded. * * @param center New world-space center of the field domain. */ setDomainCenter(center: Vector3): void; /** * Refresh skeleton, transform, and timestep state for the next dispatch. * * Reads the mesh's current world matrix and bone matrices; call after * animation has posed the skeleton and before {@link dispatch}. * * @param dt Frame timestep in seconds, used for surface-velocity estimation. * @param center Optional new domain center forwarded to {@link setDomainCenter}. */ update(dt: number, center?: Vector3): void; /** * Submit the complete field rebuild to the renderer. * * @param renderer WebGPU renderer that executes the compute batch. */ dispatch(renderer: Renderer): void; /** Names of every compute pass in dispatch order. */ getPassNames(): string[]; /** Release the owned texture, storage buffers, and compute passes; repeated calls are safe. */ dispose(): void; }