/** * USV `video` encoding — the correspondence-free tier: every frame's splats are stacked into * byte-plane atlases split across two `.af` tracks. Geometry is independently deflate-raw * compressed and stays byte-exact; appearance is a hardware-decodable 4:2:0 video. New * `shape-lossless` clips keep position, rotation, log-scale, and alpha in geometry, while * legacy plane layouts remain readable after re-baking into the current container format. * * Per-frame splat counts may vary (no correspondence needed); each frame is Morton-ordered * independently so planes are spatially smooth — the codec-efficiency lever. Playback defaults * to GPU publication and unpack (`SplatVideoClip` + `SplatVideoTextureSource`: worker inflate * plus WebCodecs appearance decode → texture uploads → unpack compute). This module's * {@link createVideoFrameLoader} remains a diagnostic CPU reference; the plane codec here is * the single source of truth both paths must match. * * Spec: `.ai/SPLAT_VIDEO_FORMAT.md`; layout constants here are the single source of truth. * * @module SplatVideoFrames */ import type { SplatVideoPlaneLayout } from './SplatVideoManifest.js'; export { VIDEO_PACKED_GEOMETRY_BITS, decodePackedGeometryDeflateFilter, encodePackedGeometryDeflateFilter, VIDEO_PACKED_GEOMETRY_FILTER, } from './SplatVideoPackedGeometry.js'; /** Plane order in the GEOMETRY track (top to bottom). */ export declare const VIDEO_GEOMETRY_PLANES: readonly ["posXHi", "posXLo", "posYHi", "posYLo", "posZHi", "posZLo", "rot0", "rot1", "rot2", "rot3"]; /** Plane order in the APPEARANCE track. */ export declare const VIDEO_APPEARANCE_PLANES: readonly ["scale0", "scale1", "scale2", "alpha", "red", "green", "blue"]; /** Shape-safe layout: every attribute affecting Gaussian coverage is lossless. */ export declare const VIDEO_SHAPE_LOSSLESS_GEOMETRY_PLANES: readonly ["posXHi", "posXLo", "posYHi", "posYLo", "posZHi", "posZLo", "rot0", "rot1", "rot2", "rot3", "scale0", "scale1", "scale2", "alpha"]; /** Shape-safe appearance track contains only visually compressible RGB. */ export declare const VIDEO_SHAPE_LOSSLESS_APPEARANCE_PLANES: readonly ["red", "green", "blue"]; /** Packed-v3 keeps alpha with the visual track and geometry in a linear 82-bit bitstream. */ export declare const VIDEO_PACKED_APPEARANCE_PLANES: readonly ["alpha", "red", "green", "blue"]; export declare const VIDEO_PACKED_POSITION_LEVELS: readonly [2047, 1023, 2047]; export declare const VIDEO_PACKED_SCALE_LEVELS = 63; /** Name of one byte plane in the lossless geometry track. */ export type SplatVideoGeometryPlane = typeof VIDEO_SHAPE_LOSSLESS_GEOMETRY_PLANES[number]; /** Name of one byte plane in the lossy appearance track. */ export type SplatVideoAppearancePlane = typeof VIDEO_APPEARANCE_PLANES[number]; /** Fixed-width spatial vector used by clip-wide quantization bounds. */ export type SplatVideoVec3 = readonly [number, number, number]; /** Motion-dilated clip bounds shared by every independently ordered frame. */ export interface SplatVideoBounds { min: SplatVideoVec3; max: SplatVideoVec3; } /** Inclusive log-scale quantization endpoints. */ export type SplatVideoScaleRange = readonly [number, number]; /** Clip-wide ranges required to encode and decode the plane atlas. */ export interface SplatVideoRanges { bounds: SplatVideoBounds; scaleRange: SplatVideoScaleRange; } /** One independently ordered, expanded splat frame accepted by the plane encoder. */ export interface SplatVideoFrame { positions: Float32Array; scales: Float32Array; rotations: Float32Array; colors: Float32Array; count: number; } /** Even-sized byte-atlas dimensions shared by the encoder and both decoder paths. */ export interface SplatVideoAtlasLayout { width: number; rowsPerPlane: number; geometryHeight: number; appearanceHeight: number; planeLayout: SplatVideoPlaneLayout; geometryPlaneCount: number; appearancePlaneCount: number; geometryBitsPerSplat: number; } /** Flat luma images produced for one frame's geometry and appearance tracks. */ export interface SplatVideoFramePlanes { geometry: Uint8Array; appearance: Uint8Array; } /** Expanded frame returned by the reference decoder. USV v1 carries no SH planes. */ export interface DecodedSplatVideoFrame extends SplatVideoFrame { shDegree: 0; } /** Normalized `manifest.video` fields consumed by the CPU reference loader. */ export interface SplatVideoFrameLoaderVideo { maxSplatCount: number; atlasWidth: number; planeLayout?: SplatVideoPlaneLayout | undefined; /** Exact, unpadded position dequantization range on normalized manifests. */ positionBounds?: SplatVideoBounds | undefined; scaleRange: SplatVideoScaleRange; counts: readonly number[]; } /** Options forwarded to the still-JavaScript `GaussianSplats` constructor. */ export interface SplatVideoFrameSplatOptions { enableSH?: boolean; [option: string]: unknown; } /** Inputs required to construct the inflate + WebCodecs reference loader. */ export interface SplatVideoFrameLoaderOptions { geometry: ArrayBuffer; appearance: ArrayBuffer; video: SplatVideoFrameLoaderVideo; /** Explicit position range for callers that do not pass a normalized video payload. */ positionBounds?: SplatVideoBounds | undefined; /** @deprecated Legacy alias for positionBounds. */ bounds?: SplatVideoBounds | undefined; } /** Structural surface used across the still-JavaScript `GaussianSplats` boundary. */ export interface SplatVideoFrameMesh { readonly isGaussianSplats: boolean; setData: (data: DecodedSplatVideoFrame) => void; dispose: () => void; } /** Ownership record expected by `SplatSequence`'s custom frame-loader seam. */ export interface SplatVideoLoadedFrame { splats: SplatVideoFrameMesh; owned: true; reusable: true; } /** Async frame callback installed into `SplatSequence`. */ export type SplatVideoFrameLoader = (source: unknown, frameIndex: number, splatOptions: Readonly) => Promise; /** Ready loader plus the paired decoder disposal hook. */ export interface SplatVideoFrameLoaderResult { loadFrame: SplatVideoFrameLoader; dispose: () => void; } /** * Atlas layout for `maxSplatCount` splats: splat i lives at texel (i % width, plane·rows + * floor(i / width)). Width and total height are kept even (yuv420 requirement). * * @param {number} maxSplatCount - Capacity every frame is padded to. * @param {number} [width=1024] - Atlas width in texels. * @param {string} [planeLayout='legacy'] - Attribute-to-track assignment from the manifest. * @returns {{ width:number, rowsPerPlane:number, geometryHeight:number, appearanceHeight:number, planeLayout:string, geometryPlaneCount:number, appearancePlaneCount:number }} */ export declare function videoAtlasLayout(maxSplatCount: number, width?: number, planeLayout?: SplatVideoPlaneLayout): SplatVideoAtlasLayout; /** * Quantize one frame into the two mono plane images (geometry, appearance) as flat * `Uint8Array`s of `width × height` luma bytes. Splats beyond `frame.count` are zero. * * @param {Object} frame - `{ positions, scales, rotations, colors, count }` (Morton-ordered). * @param {Object} layout - From {@link videoAtlasLayout}. * @param {Object} ranges - `{ bounds:{min,max}, scaleRange:[min,max] }` (clip-global). * @returns {{ geometry:Uint8Array, appearance:Uint8Array }} */ export declare function encodeFramePlanes(frame: SplatVideoFrame, layout: SplatVideoAtlasLayout, ranges: SplatVideoRanges): SplatVideoFramePlanes; /** * Decode one frame's splat arrays from the two luma plane images (the exact inverse of * {@link encodeFramePlanes} up to quantization + whatever the lossy track cost). * * @param {Uint8Array} geometry - Geometry-track luma bytes (width × geometryHeight). * @param {Uint8Array} appearance - Appearance-track luma bytes. * @param {number} count - This frame's splat count. * @param {Object} layout - From {@link videoAtlasLayout}. * @param {Object} ranges - Same ranges the encoder used. * @returns {{ positions:Float32Array, scales:Float32Array, rotations:Float32Array, colors:Float32Array, count:number, shDegree:number }} */ export declare function decodeFramePlanes(geometry: Uint8Array, appearance: Uint8Array, count: number, layout: SplatVideoAtlasLayout, ranges: SplatVideoRanges): DecodedSplatVideoFrame; /** Exact byte length of one tightly packed-v3 geometry member. */ export declare function packedGeometryByteLength(count: number): number; /** * Browser playback bridge: a `SplatSequence` frame loader that inflates geometry, decodes * appearance through WebCodecs, and expands both plane atlases into splat arrays. * Usage (what `SplatClip.load` does for `encoding:'video'` manifests): * * const loader = await createVideoFrameLoader( { geometryAF, appearanceAF, manifest } ); * SplatSequence.load( manifest.frameLabels, { loadFrame: loader.loadFrame, … } ) * * Appearance arrives as a `VideoFrame`; luma is extracted via `copyTo` (RGBA fallback when * the UA refuses planar reads). Geometry arrives as byte-exact inflated bytes. The per-frame * CPU expand is this path's deliberate cost — it is the reference implementation the GPU * texture-unpack path (`SplatVideoClip`, the default) is validated against. * * @param {Object} options - { geometry:ArrayBuffer, appearance:ArrayBuffer, video:Object (manifest.video), bounds:Object }. * @returns {Promise<{ loadFrame:Function, dispose:Function }>} */ export declare function createVideoFrameLoader(options: SplatVideoFrameLoaderOptions): Promise;