/** * VAVMesh streams stable-topology deformation as a regular `THREE.Mesh` on WebGPURenderer. * Construction needs only the manifest and topology sidecar. Exact UTSBM numerical tracks * (meshopt-coded block assets) reconstruct the two R8 `DataTexture` frame slots sampled by * the vertex shader. * * Optional UV-texture appearance remains visual media: WebCodecs produces `VideoFrameTexture` * bracketing pairs for GPU publication without CPU pixel readback, then the fragment stage * samples by UV. Wire the results via {@link VAVMesh#aoNode}, * {@link VAVMesh#colorNode}, {@link VAVMesh#giNode}, or per pass via * {@link VAVMesh#textureNodes}. * * ```js * const vav = await VAVMesh.load( '/assets/vav/vav-basic/manifest.json' ); * scene.add( vav ); * vav.play(); * // in the render loop: * vav.update( delta ); * ``` * * @class VAVMesh * @short Streaming stable-topology deformation mesh with exact numerical tracks. * @category VAV */ import * as THREE from 'three/webgpu'; import type { MeshoptVertexDecoderLike } from '../MotionTracks/IndexedMeshoptTrack.cjs'; import type { VAVBase, VAVManifest } from './VAVManifest.cjs'; import { VAVMeshoptDecoder } from './VAVMeshoptBinary.cjs'; import type { VAVTrackFetch, VAVTrackFetchResponse } from './VAVTrackStream.cjs'; import type { TSLFloatNode, TSLNode, TSLUniformNode, TSLVec3Node } from '../types/tsl.cjs'; /** Promise settled by the first decoded VAV frame or early disposal. */ export interface VAVMeshDeferred extends Promise { resolve(value: TValue | PromiseLike): void; } /** Appearance source selection when a clip contains vertex and UV tracks. */ export type VAVMeshAppearance = 'auto' | 'uv' | 'vertex'; /** URL resolver used for manifest-relative VAV track paths. */ export type VAVMeshFileResolver = (file: string) => string; /** Fetch response needed while loading the manifest, topology, and progressive tracks. */ export interface VAVMeshFetchResponse extends VAVTrackFetchResponse { json(): Promise; arrayBuffer(): Promise; } /** Injectable fetch contract for {@link VAVMesh.load}. */ export type VAVMeshFetch = (input: string, init?: RequestInit) => PromiseLike; /** Constructor options for a parsed VAV clip. */ export interface VAVMeshOptions { material?: THREE.NodeMaterial; loop?: boolean; playbackSpeed?: number; minBufferedSeconds?: number; appearance?: VAVMeshAppearance; resolveFile?: VAVMeshFileResolver; fetchImpl?: VAVTrackFetch; /** * Meshopt vertex decoder for the UTSBM numerical tracks — pass `MeshoptDecoder` from * `three/addons/libs/meshopt_decoder.module.js` (the same module `GLTFLoader` uses). */ meshoptDecoder?: MeshoptVertexDecoderLike; } /** Network-load options; URL resolution is owned by {@link VAVMesh.load}. */ export interface VAVMeshLoadOptions extends Omit { fetchImpl?: VAVMeshFetch; } /** Accepted in-memory clip tree used by {@link VAVMesh.loadFromFiles}. */ export type VAVMeshFiles = Map | Record; /** Scalar or RGB appearance node exposed by dynamic VAV plane/pass names. */ export type VAVMeshAppearanceNode = TSLFloatNode | TSLVec3Node; export interface VAVNumericalTrackDiagnostics { key: 'geometry' | 'appearance'; presentedDecodedSlots: 2; decoder: ReturnType; } export interface VAVMeshDiagnostics { numericalTracks: VAVNumericalTrackDiagnostics[]; uvVideoDecoderCount: number; suspended: boolean; disposed: boolean; } export declare class VAVMesh extends THREE.Mesh { manifest: Readonly; loop: boolean; playbackSpeed: number; minBufferedSeconds: number; playing: boolean; time: number; blend: TSLUniformNode<'float', number>; firstFrame: VAVMeshDeferred; appearance: VAVMeshAppearance; positionNode: TSLVec3Node; normalLocalNode: TSLVec3Node | null; channelNodes: Record; textureNodes: Record; aoNode: VAVMeshAppearanceNode | null; colorNode: VAVMeshAppearanceNode | null; giNode: VAVMeshAppearanceNode | null; bakedNode: TSLNode | null; giLighting: 'full' | 'indirect' | null; irradianceNode: TSLNode | null; private _lastFrame; private _held; private _appliedLo; private _buffering; private _disposed; private _suspended; private _resumePromise; private _lifecycleGeneration; private _tracks; private _disposedNumericalDiagnostics; /** * Fetch a VAV clip's manifest + topology and build a playable mesh. Resolves as soon as * the clip is constructed — tracks keep streaming in the background and playback begins * once `minBufferedSeconds` of both tracks is available. * * @param {string} url - URL of `manifest.json` (track/base paths resolve against it). * @param {Object} [options={}] - See the constructor. * @returns {Promise} */ static load(url: string, options?: VAVMeshLoadOptions): Promise; /** * Build a playable mesh from an in-memory clip tree (drag-and-drop, File System Access, etc.). * Keys are manifest-relative paths: `manifest.json`, `base.bin`, `tracks/geometry/000.utsbm`, … * * @param {Map|Record} files * @param {Object} [options={}] - See the constructor. * @returns {Promise} */ static loadFromFiles(files: VAVMeshFiles, options?: VAVMeshOptions): Promise; /** * @param {Object} manifest - A manifest from {@link parseVAVManifest}. * @param {{ indices:Uint32Array, uv:Float32Array|null }} base - Parsed topology sidecar. * @param {Object} [options={}] * @param {THREE.NodeMaterial} [options.material] - Material to drive (default `MeshStandardNodeMaterial`). More can join via {@link VAVMesh#registerMaterial}. * @param {boolean} [options.loop=true] - Loop playback. * @param {number} [options.playbackSpeed=1] * @param {number} [options.minBufferedSeconds=0.5] - Stream cushion required to start / resume after a stall. * @param {'auto'|'uv'|'vertex'} [options.appearance='auto'] - Which appearance kind to load * when the clip carries both: `'auto'` loads everything and the sugar nodes prefer the * UV textures; `'uv'` skips the per-vertex appearance track; `'vertex'` skips the UV * texture tracks (the lightweight option — no video texture decoders at all). * @param {(file:string) => string} [options.resolveFile] - Track path → URL resolver (set by {@link VAVMesh.load}). * @param {Function} [options.fetchImpl] - Injectable fetch for the track streams (tests). */ constructor(manifest: Readonly, base: VAVBase, options?: VAVMeshOptions); private _buildNodes; private _mixedPlane; /** * Wire a NodeMaterial to this clip: assigns `positionNode` (and `normalNode` when the * geometry track carries normals). Per-vertex beauty planes are exposed for manual * wiring — `material.aoNode = vav.aoNode`, `material.colorNode = vav.colorNode`, or any * named plane via {@link VAVMesh#channelNodes} (e.g. `vav.channelNodes.gi`). * * @param {THREE.NodeMaterial} material - Material to drive. * @returns {THREE.NodeMaterial} The same material. */ registerMaterial(material: TMaterial): TMaterial; /** * Merge the baked GI into a LIT material's indirect diffuse (lightmap semantics) so * dynamic lights provide direct lighting on top of the baked bounce light. Overrides * `NodeMaterial#setupLightMap` on this material instance; `material.aoNode` then occludes * it like any indirect light (three's AO applies to indirect terms only). * * Meant for a `gi` pass baked INDIRECT-ONLY ({@link VAVMesh#giLighting} `'indirect'`, the * addon's "GI Includes Direct Light" OFF) — wiring a `'full'` bake here double-counts * direct light. Undo with `delete material.setupLightMap`. * * @param {THREE.NodeMaterial} material - Lit material to receive the irradiance. * @returns {THREE.NodeMaterial} The same material. */ registerIrradiance(material: TMaterial): TMaterial; /** Clip duration in seconds. */ get duration(): number; /** Whether playback is currently stalled waiting for the stream. */ get isBuffering(): boolean; /** Begin (or resume) playback; actual advancement is gated by the stream cushion. */ play(): void; /** Pause playback, holding the current pair on screen. */ pause(): void; /** * Scrub to a time in seconds (clamped to the clip). The pair is applied once its bytes * are available. * * @param {number} seconds - Target time. */ setTime(seconds: number): void; /** * Advance the playback clock. Call once per rendered frame. The clock only moves while * the next needed frame is byte-available on every track; otherwise it stalls on the * current pair and resumes with a `minBufferedSeconds` cushion. * * @param {number} delta - Elapsed seconds since the previous call. */ update(delta: number): void; /** * Abort numerical reads and release every active UV media decoder while preserving the two * presented texture slots, live UV frames, nodes, mesh bindings, and playback position. */ suspend(): void; /** Recreate released decoders and continue every suspended stream without replacing textures. */ resume(): Promise; private _availableFrames; private _apply; private _loadPair; private _requestTrackFrames; private _blendFactor; private _prime; private _failDataDecoder; private _createUVDecoder; private _fallbackUVDecoder; private _onUVFrame; private _writeSlot; /** Snapshot numerical binary and optional UV media-decoder state. */ getDiagnostics(): Readonly; /** * Seconds of playback buffered ahead of the playhead (capped at clip end) — handy for a * buffering UI. * * @returns {number} */ bufferedSecondsAhead(): number; /** * Release decoders, streams, textures, and the geometry. The material is left to its * owner (it may be shared). Idempotent. */ dispose(): void; }