import type { Renderer, StorageBufferNode } from 'three/webgpu'; import { SplatSourceResource } from './SplatSourceResource.js'; import type { ExpandedSplatGPUResources, ExpandedSplatSourceData, SplatSourceReadNodes, SplatSourceResourceOptions } from './SplatSourceResource.js'; import type { KeyframeGaussianResult, SpacetimeGaussianResult } from './SpacetimeMotion.js'; import type { SplatVideoPackedWindow } from './SplatVideoCodec.js'; /** Four Catmull-Rom basis coefficients in knot order. */ export type CatmullRomWeights = [number, number, number, number]; /** Mutable playback state exposed by the spacetime source. */ export interface SpacetimeSplatTimeState { seconds: number; frame: number; windowIndex: number; segment: number; u: number; } /** Analytic STG arrays for the dynamic tail. */ export interface SpacetimeSplatSTGData { motion: Float32Array; omega: Float32Array; trbfCenter: Float32Array; trbfScale: Float32Array; } interface SpacetimeSplatPayloadCommon { dynamicCount: number; frameCount: number; frameRate?: number | undefined; } /** Windowed keyframe-track payload assembled by {@link SplatClip}. */ export interface SpacetimeSplatKeyframesPayload extends SpacetimeSplatPayloadCommon { kind: 'keyframes'; windowFrames: number; knotStride: number; hasRotation?: boolean | undefined; windows: Array; lifespanWords?: Uint32Array | null | undefined; residency?: 'full' | 'windowed' | undefined; } /** Analytic spacetime-Gaussian payload assembled by {@link SplatClip}. */ export interface SpacetimeSplatSTGPayload extends SpacetimeSplatPayloadCommon { kind: 'stg'; stg: SpacetimeSplatSTGData; } /** Temporal payload discriminated by its track model. */ export type SpacetimeSplatPayload = SpacetimeSplatKeyframesPayload | SpacetimeSplatSTGPayload; /** Expanded static attributes plus temporal dynamic-tail tracks. */ export interface SpacetimeSplatData extends ExpandedSplatSourceData { spacetime: SpacetimeSplatPayload; } /** Construction options for {@link SpacetimeSplatSource}. */ export type SpacetimeSplatSourceOptions = Pick; /** GPU allocations owned by the spacetime source and its expanded inner source. */ export interface SpacetimeSplatGPUResources extends ExpandedSplatGPUResources { spacetimeBasePositions: StorageBufferNode<'vec3'>; spacetimeBaseRotations: StorageBufferNode<'vec4'>; spacetimeBaseColors: StorageBufferNode<'vec4'>; spacetimePosKnots?: StorageBufferNode<'uint'> | undefined; spacetimeRotKnots?: StorageBufferNode<'uint'> | undefined; spacetimeLifespans?: StorageBufferNode<'uint'> | undefined; spacetimeMotion?: StorageBufferNode<'vec3'> | undefined; spacetimeOmega?: StorageBufferNode<'vec4'> | undefined; spacetimeTrbf?: StorageBufferNode<'vec2'> | undefined; } /** Narrow GaussianSplats surface used for temporal uploads and resolve dispatches. */ export interface SpacetimeSplatMesh { buffers: (SpacetimeSplatGPUResources & { chunkBounds?: StorageBufferNode<'float'> | null | undefined; }) | null; writeChunkBoundsRange(firstChunk: number, bounds: Float32Array): void; } /** Common evaluated tail pose returned by both temporal models. */ export type EvaluatedSpacetimeTail = KeyframeGaussianResult | (SpacetimeGaussianResult & { weight: number; }); /** * Catmull-Rom basis weights for a local parameter u — the CPU half of the uniform-driven * segment evaluation (all splats share one clip time, so the basis is per-dispatch constant). * Matches `evaluateCatmullRom` exactly. * * @param {number} u - Local segment parameter in [0, 1]. * @returns {number[]} [w0, w1, w2, w3] control-point weights. */ export declare function catmullRomWeights(u: number): CatmullRomWeights; /** * GPU source for USV `tracks` clips: a static prefix plus a dynamic tail whose attributes are * produced by a RESOLVE COMPUTE PASS — one dispatch over the tail per time change, writing the * evaluated position / rotation / lifespan-weighted alpha DIRECTLY INTO the expanded static * buffers' tail slots. `buildReadNodes()` is therefore exactly the inner static source's read * nodes: projection, the raster vertex shader, shadows, SH, and tiles bind ZERO additional * storage buffers (the WebGPU baseline allows only 8 per compute stage — adding per-consumer * evaluated-buffer bindings blows that limit) and pay ZERO extra per-read cost. * * Pristine per-tail base copies (position / rotation / color) live in small buffers bound ONLY * by the resolve kernel, so every resolve recomputes from the rest pose — no accumulation * drift, and the kernel lands on exactly ≤8 storage bindings for both track kinds. * * All time-dependent state lives in uniforms (segment plane base, Catmull-Rom weights, window * dequantization ranges, time): window switches touch uniforms only and the compute graph * never rebuilds. Track math mirrors `SpacetimeMotion.js` (the CPU twin — spec truth); the * inner expanded buffers are PBO-enabled, so goldens read the tail slots back for parity. * * Expected `data.spacetime` payload (assembled by `SplatClip` or tests): * - `kind`: `'keyframes' | 'stg'` * - `dynamicCount`, `frameCount`, `frameRate` * - keyframes: `windowFrames`, `knotStride`, `hasRotation`, * `windows` (ordered `readWindowPacked` results), `lifespanWords` (Uint32Array|null) * - stg: `stg: { motion, omega, trbfCenter, trbfScale }` (CPU-decoded Float32Arrays; * trbf values in seconds) * * @class SpacetimeSplatSource * @augments SplatSourceResource * @short Resolve-pass GPU source for temporal (USV) splat clips. * @category GaussianSplatting */ export declare class SpacetimeSplatSource extends SplatSourceResource { kind: 'keyframes' | 'stg'; dynamicCount: number; staticCount: number; frameCount: number; frameRate: number; hasRotation: boolean; timeState: SpacetimeSplatTimeState; resolveDirty: boolean; windowFrames: number; knotStride: number; knotCount: number; segments: number; lifespanWords: Uint32Array | null; residency: 'full' | 'windowed'; windowCount: number; private _inner; private _baseTailPositions; private _baseTailRotations; private _baseTailColors; private _resolveCompute; private _decodedWindows; private _windows; private _slotCount; private _slotWindow; private _planeWords; private _positionSlab; private _rotationSlab; private _slabBytes; private _stg; private _uTimeFrame; private _uTimeSeconds; private _uPlaneBase; private _uCRWeights; private _uU; private _uDMin; private _uDRange; constructor(data: SpacetimeSplatData, count: number, options?: SpacetimeSplatSourceOptions); private _initKeyframes; private _initSTG; /** * Set the playback time. Computes the window/segment/basis on the CPU (uniform per dispatch * — every splat shares the clip time), updates uniforms, and marks the resolve pass dirty. * The caller (SplatClip) is responsible for `invalidate()` + shadow/SH invalidation. * * @param {number} seconds - Clip time in seconds. * @returns {this} */ setTime(seconds: number): this; /** Window covering a continuous frame position. */ windowIndexForFrame(frame: number): number; _slotOfWindow(windowIndex: number): number; /** Whether a window's knot slabs are resident (always true under full residency). */ isWindowResident(windowIndex: number): boolean; /** * Stream a fetched window into its slab slot (windowed residency): ranged upload of the * knot planes, header retained for uniforms/CPU mirrors, the evicted window's CPU copies * released. Cull bounds are NOT touched here — they apply on ACTIVATION (see * {@link SpacetimeSplatSource#applyWindowBounds}) so the currently-playing window keeps its * own bounds until the playhead actually crosses. * * @param {GaussianSplats} splats - The mesh whose buffers this source populated. * @param {Object} packed - A `readWindowPacked` result. * @returns {this} */ uploadWindow(splats: SpacetimeSplatMesh, packed: SplatVideoPackedWindow): this; /** * Write a window's motion-dilated cull-chunk bounds into the mesh (leak-checklist item 1, * windowed flavor). Call on window ACTIVATION — bounds are shared tail-chunk state, so the * next window's bounds must not replace the playing window's early. * * @param {GaussianSplats} splats - The mesh whose buffers this source populated. * @param {number} windowIndex - The window becoming active. * @returns {this} */ applyWindowBounds(splats: SpacetimeSplatMesh, windowIndex: number): this; private _applyWindowUniforms; createGPUResources(): SpacetimeSplatGPUResources; upload(buffers: SpacetimeSplatGPUResources): void; rangeSignature(): number[]; buildReadNodes(buffers: SpacetimeSplatGPUResources): SplatSourceReadNodes; /** * Dispatch the resolve pass when time changed since the last dispatch. Builds the compute * node lazily on first use (buffers exist by then — call from the mesh's `beforeupdate`). * * @param {THREE.WebGPURenderer} renderer - Active renderer. * @param {GaussianSplats} splats - The mesh whose buffers this source populated. * @returns {boolean} Whether a dispatch happened. */ updateResolve(renderer: Renderer, splats: SpacetimeSplatMesh): boolean; private _buildResolve; private _buildKeyframesKernel; private _buildSTGKernel; private _decodedWindow; private _lifespanOf; private _evaluateTailCPU; readPositionsCPU(): Float32Array; readRotationsCPU(): Float32Array; readColorsCPU(): Float32Array; dispose(): void; } export {};