/** * Utsubo Splat Video (USV v1) codec — shared by the Node packer * (tools/splat-video-export.mjs) and the browser runtime (SplatClip / SpacetimeSplatSource). * * Platform-neutral TypeScript, no browser or Node APIs: typed arrays in, typed arrays out. * Format specification: packages/core/.ai/SPLAT_VIDEO_FORMAT.md * * A clip stores its temporal payload for the DYNAMIC TAIL only (see the static/dynamic * partition in the spec) as: * - `windows/N.usw` — per-window knot slabs (keyframes track kind): knot-major planes of * 11-10-11 position deltas (from the DECODED static base) and smallest-three rotations, * with per-window quantization ranges, phantom boundary knots for C1 continuity, and * motion-dilated cull-chunk bounds. * - `tracks/clip.ust` — per-clip track data: flat-top lifespans (keyframes kind) or packed * float16 STG records (splaTV / Spacetime Gaussians import). * * Quantization primitives are shared with the USS codec (SplatStreamCodec) so `.usc` and * `.usw` stay bit-compatible conventions of one family. * * @module SplatVideoCodec */ /** Uniform timeline dimensions used by the keyframe window codec. */ export interface SplatVideoWindowGridOptions { frameCount: number; windowFrames: number; knotStride: number; } /** Derived window/knot layout for a keyframe clip. */ export interface SplatVideoWindowGrid { windowCount: number; segments: number; knotCount: number; knotFrame: (windowIndex: number, storedIndex: number) => number; idealKnotFrame: (windowIndex: number, storedIndex: number) => number; } /** One analytic keyframe sample for a dynamic-tail splat. */ export interface SplatVideoTrackSample { dx: number; dy: number; dz: number; qx: number; qy: number; qz: number; qw: number; } /** Samples one dynamic-tail splat at an integer timeline frame. */ export type SplatVideoTrackSampler = (splatIndex: number, frame: number) => SplatVideoTrackSample; /** Input planes and metadata used to encode one `.usw` window. */ export interface SplatVideoWindowInput { windowIndex: number; frameStart: number; knotStride: number; knotCount: number; dynamicCount: number; positionDeltas: Float32Array; rotations?: Float32Array | null; chunkFirst?: number; chunkBounds?: Float32Array | null; } interface SplatVideoWindowRecordBase { windowIndex: number; frameStart: number; knotStride: number; knotCount: number; dynamicCount: number; dMin: [number, number, number]; dMax: [number, number, number]; chunkFirst: number; chunkBounds: Float32Array | null; } /** Packed `.usw` header and GPU-ready word planes, with rotation presence discriminated. */ export type SplatVideoPackedWindow = SplatVideoWindowRecordBase & ({ hasRotation: true; positionWords: Uint32Array; rotationWords: Uint32Array; } | { hasRotation: false; positionWords: Uint32Array; rotationWords: null; }); /** Expanded `.usw` window, with rotation presence discriminated. */ export type SplatVideoDecodedWindow = SplatVideoWindowRecordBase & ({ hasRotation: true; positionDeltas: Float32Array; rotations: Float32Array; } | { hasRotation: false; positionDeltas: Float32Array; rotations: null; }); /** Quantized lifespan source values, expressed in timeline frames. */ export interface SplatVideoLifespanInput { birth: Float32Array; death: Float32Array; ramp: Float32Array; } /** Decoded flat-top lifespan arrays. */ export interface SplatVideoLifespans { birth: Float32Array; death: Float32Array; ramp: Uint8Array; } /** Keyframe-kind `clip.ust` encoder input. */ export interface SplatVideoKeyframeTracksInput { kind: 'keyframes'; dynamicCount: number; frameCount: number; lifespans?: SplatVideoLifespanInput | null; } /** Spacetime-Gaussian-kind `clip.ust` encoder input. */ export interface SplatVideoSTGTracksInput { kind: 'stg'; dynamicCount: number; frameCount: number; motion: Float32Array; omega: Float32Array; trbfCenter: Float32Array; trbfScale: Float32Array; /** 'high' stores f32 records (64 B/splat) — required for room-scale scenes; default packed halves. */ precision?: 'compact' | 'high'; } /** Discriminated input accepted by {@link encodeClipTracks}. */ export type SplatVideoClipTracksInput = SplatVideoKeyframeTracksInput | SplatVideoSTGTracksInput; /** Decoded keyframe-kind `clip.ust` record. */ export type SplatVideoDecodedKeyframeTracks = { kind: 'keyframes'; dynamicCount: number; frameCount: number; hasLifespan: true; lifespans: SplatVideoLifespans; } | { kind: 'keyframes'; dynamicCount: number; frameCount: number; hasLifespan: false; lifespans: null; }; /** Decoded spacetime-Gaussian-kind `clip.ust` record. */ export interface SplatVideoDecodedSTGTracks { kind: 'stg'; dynamicCount: number; frameCount: number; motion: Float32Array; omega: Float32Array; trbfCenter: Float32Array; trbfScale: Float32Array; } /** Discriminated result returned by {@link decodeClipTracks}. */ export type SplatVideoDecodedClipTracks = SplatVideoDecodedKeyframeTracks | SplatVideoDecodedSTGTracks; /** GPU-ready lifespan words parsed from a keyframe-kind `clip.ust`. */ export type SplatVideoPackedClipTracks = { kind: 'keyframes'; dynamicCount: number; frameCount: number; hasLifespan: true; lifespanWords: Uint32Array; } | { kind: 'keyframes'; dynamicCount: number; frameCount: number; hasLifespan: false; lifespanWords: null; }; /** Options for sampling analytic tracks into knot-major window planes. */ export interface SplatVideoSynthesizeWindowOptions extends SplatVideoWindowGridOptions { windowIndex: number; dynamicCount: number; rotation?: boolean; } /** Knot-major planes synthesized for one window. */ export interface SplatVideoSynthesizedWindow { windowIndex: number; frameStart: number; knotStride: number; knotCount: number; dynamicCount: number; positionDeltas: Float32Array; rotations: Float32Array | null; } export declare const USW_MAGIC = 827806549; export declare const USW_VERSION = 1; export declare const USW_HEADER_BYTES = 72; export declare const UST_MAGIC = 827609941; export declare const UST_VERSION = 1; export declare const UST_HEADER_BYTES = 32; export declare const UST_STG_RECORD_BYTES = 32; /** Header flags (offset 20), STG kind: bit 0 = f32 records (64 B) instead of packed halves. */ export declare const UST_FLAG_STG_F32_RECORDS = 1; export declare const UST_STG_RECORD_F32_BYTES = 64; export declare const VIDEO_MANIFEST_TYPE = "utsubo-splat-video"; export declare const VIDEO_MANIFEST_VERSION = 1; /** Track kinds carried by `tracks/clip.ust` (u32 header field). */ export declare const TRACK_KIND_KEYFRAMES = 0; export declare const TRACK_KIND_STG = 1; /** Binary payload accepted by the USV codec readers. */ export type SplatVideoBinaryInput = ArrayBuffer | Uint8Array; /** * Convert a float32 to an IEEE 754 binary16 bit pattern (round-to-nearest-even). * * @param {number} value Finite float (Inf/NaN pass through as their half encodings). * @returns {number} 16-bit half-float bits. */ export declare function toHalf(value: number): number; /** * Convert an IEEE 754 binary16 bit pattern to float32. * * @param {number} half 16-bit half-float bits. * @returns {number} The float value. */ export declare function fromHalf(half: number): number; /** * Derive the uniform knot grid a clip's keyframe windows tile the timeline with. * * Window `w` starts at frame `w·windowFrames`, has `segments = windowFrames / knotStride` * Catmull-Rom segments, and stores `segments + 3` knots: one pre-phantom, the real knots * including both window boundaries, and one post-phantom. All knot frames clamp to * `[0, frameCount-1]` (clip-edge phantoms degenerate to duplicated end knots — the clamped * tangent boundary condition; the final window's grid pads past the last frame the same way). * * @param {Object} options - { frameCount, windowFrames, knotStride }. * @returns {{ windowCount:number, segments:number, knotCount:number, knotFrame:Function }} * `knotFrame(windowIndex, storedIndex)` maps a stored knot slot to its (clamped) frame. */ export declare function windowGrid(options: SplatVideoWindowGridOptions): SplatVideoWindowGrid; /** * Sample a per-frame position quantity at a knot's IDEAL frame, extending past the clip edges * by ODD REFLECTION: `S(-d) := 2·S(0) − S(d)` and `S(last+d) := 2·S(last) − S(last−d)` — a * constant-velocity extension. A clamped (duplicated) edge phantom would halve the Catmull-Rom * boundary tangent and bend every boundary segment inward (measured ~5% of the motion * amplitude on fast sines); reflection keeps the edge tangent first-order accurate. Rotation * phantoms stay clamped duplicates — nlerp never reads the phantom planes. * * @param {Function} sampleAt - `sampleAt(frame) → number` for in-range integer frames. * @param {number} frame - The knot's ideal (possibly out-of-range) frame. * @param {number} frameCount - Timeline frames. * @returns {number} The (possibly reflected) sample. */ export declare function sampleKnotWithReflection(sampleAt: (frame: number) => number, frame: number, frameCount: number): number; /** * Encode one window's knot slabs into a `.usw` payload. * * Knot arrays are KNOT-MAJOR: plane `j` (all splats' knot `j`) occupies indices * `[j·dynamicCount, (j+1)·dynamicCount)` — coalesced GPU gathers and spatially smooth planes * (Morton order) for the reserved image/video carriers. * * @param {Object} window - Window description + data. * @param {number} window.windowIndex - Index in the clip's window sequence. * @param {number} window.frameStart - First timeline frame (= windowIndex · windowFrames). * @param {number} window.knotStride - Frames between knots (K). * @param {number} window.knotCount - Stored knots (segments + 3, incl. the two phantoms). * @param {number} window.dynamicCount - Dynamic-tail splat count (D). * @param {Float32Array} window.positionDeltas - knotCount·D·3 deltas from the DECODED static base, knot-major. * @param {Float32Array|null} [window.rotations] - knotCount·D·4 absolute xyzw quaternions, knot-major. * @param {number} [window.chunkFirst=0] - First 256-splat cull chunk covered by the dynamic tail. * @param {Float32Array|null} [window.chunkBounds] - chunkCount·6 motion-dilated AABBs (min xyz, max xyz). * @returns {ArrayBuffer} `.usw` payload. */ export declare function encodeWindow(window: SplatVideoWindowInput): ArrayBuffer; /** * Parse a `.usw` payload into its header plus RAW quantized word planes — the form the GPU * uploads directly (the resolve kernel dequantizes with the header ranges as uniforms). * Handles unaligned views (zip-extracted slices) via an aligned copy. * * @param {ArrayBuffer|Uint8Array} input `.usw` payload. * @returns {{ windowIndex:number, frameStart:number, knotStride:number, knotCount:number, * dynamicCount:number, hasRotation:boolean, dMin:number[], dMax:number[], * chunkFirst:number, chunkBounds:Float32Array|null, * positionWords:Uint32Array, rotationWords:Uint32Array|null }} */ export declare function readWindowPacked(input: SplatVideoBinaryInput): SplatVideoPackedWindow; /** * Decode a `.usw` payload into knot-major float arrays (the encoder's exact inverse up to * quantization) — the CPU-twin / packer-validation form. GPU consumers use * {@link readWindowPacked} instead and dequantize in-kernel. * * @param {ArrayBuffer|Uint8Array} input `.usw` payload. * @returns {{ windowIndex:number, frameStart:number, knotStride:number, knotCount:number, * dynamicCount:number, hasRotation:boolean, dMin:number[], dMax:number[], * chunkFirst:number, chunkBounds:Float32Array|null, * positionDeltas:Float32Array, rotations:Float32Array|null }} */ export declare function decodeWindow(input: SplatVideoBinaryInput): SplatVideoDecodedWindow; /** * Encode the per-clip track file: flat-top lifespans (keyframes kind) or packed-half STG * records (splaTV / Spacetime Gaussians import). * * @param {Object} clip - Clip track data. * @param {'keyframes'|'stg'} clip.kind - Track kind. * @param {number} clip.dynamicCount - Dynamic-tail splat count (D). * @param {number} clip.frameCount - Timeline frames (lifespan quantization domain). * @param {Object|null} [clip.lifespans] - keyframes kind: { birth, death, ramp } arrays (frames), or null when every splat lives the whole clip. * @param {Float32Array} [clip.motion] - stg kind: D·9 cubic position coefficients. * @param {Float32Array} [clip.omega] - stg kind: D·4 linear quaternion velocities. * @param {Float32Array} [clip.trbfCenter] - stg kind: D temporal centers (seconds). * @param {Float32Array} [clip.trbfScale] - stg kind: D temporal widths (seconds, pre-exp'd). * @returns {ArrayBuffer} `.ust` payload. */ export declare function encodeClipTracks(clip: SplatVideoClipTracksInput): ArrayBuffer; /** * Decode a `.ust` payload (the encoder's exact inverse up to quantization). * * @param {ArrayBuffer|Uint8Array} input `.ust` payload. * @returns {{ kind:'keyframes', dynamicCount:number, frameCount:number, hasLifespan:boolean, * lifespans:{ birth:Float32Array, death:Float32Array, ramp:Uint8Array }|null } | * { kind:'stg', dynamicCount:number, frameCount:number, motion:Float32Array, * omega:Float32Array, trbfCenter:Float32Array, trbfScale:Float32Array }} */ export declare function decodeClipTracks(input: SplatVideoBinaryInput): SplatVideoDecodedClipTracks; /** * Parse a keyframes-kind `.ust` payload into its RAW lifespan words — the form the GPU uploads * directly (the resolve kernel dequantizes with `frameCount` baked in). STG-kind payloads have * no packed GPU form (records are CPU-decoded to float buffers at load); use * {@link decodeClipTracks} for those. * * @param {ArrayBuffer|Uint8Array} input `.ust` payload. * @returns {{ kind:'keyframes', dynamicCount:number, frameCount:number, hasLifespan:boolean, * lifespanWords:Uint32Array|null }} */ export declare function readClipTracksPacked(input: SplatVideoBinaryInput): SplatVideoPackedClipTracks; /** * Sample an analytic per-splat track into one window's knot-major arrays, ready for * {@link encodeWindow}. This is how test fixtures (and the packer, for already-fitted knots) * produce windows without depending on trajectory fitting. * * @param {Function} sample - `sample(splatIndex, frame)` → `{ dx, dy, dz, qx, qy, qz, qw }` * (position delta from the decoded static base + absolute rotation at that frame). * @param {Object} options - { windowIndex, frameCount, windowFrames, knotStride, dynamicCount, rotation=true }. * @returns {{ windowIndex:number, frameStart:number, knotStride:number, knotCount:number, * dynamicCount:number, positionDeltas:Float32Array, rotations:Float32Array|null }} */ export declare function synthesizeWindowKnots(sample: SplatVideoTrackSampler, options: SplatVideoSynthesizeWindowOptions): SplatVideoSynthesizedWindow; export {};