import * as THREE from 'three/webgpu'; import { AFRangeSource } from '../Video/AFRangeSource.js'; import { type BakedMotionCoarseFloor } from './BakedMotionCoarse.js'; import { AFVideo } from '../Video/AFVideo.js'; import type { AFManifest } from '../Video/AFContainer.js'; import type { AFEncodedSource } from '../Video/AFEncodedSource.js'; import type { BakedMotionFrameSample, BakedMotionIndexedRendition, BakedMotionManifest, BakedMotionVector3Like } from './BakedMotionManifest.js'; import { BakedMotionError } from './BakedMotionError.js'; import type { BakedMotionDecoderAttemptDiagnostics, BakedMotionDiagnostics, BakedMotionState } from './BakedMotionError.js'; export { BakedMotionError }; export type { BakedMotionDiagnostics, BakedMotionErrorCode, BakedMotionState, } from './BakedMotionError.js'; import type { TSLFloatNode, TSLUniformNode, TSLVec2Node, TSLVec4Node } from '../types/tsl.js'; /** Media delivery policy: one whole-file request per track (default) or opt-in verified range segments. */ export type BakedMotionDelivery = 'full' | 'segments'; export type BakedMotionMaterial = THREE.NodeMaterial; export type BakedMotionGeometry = THREE.BufferGeometry; export type BakedMotionMesh = THREE.Mesh; export interface BakedMotionFetchResponse { readonly ok: boolean; readonly status: number; readonly headers?: { get(name: string): string | null; }; readonly body?: ReadableStream | null; json(): Promise; text?: () => Promise; arrayBuffer(): Promise; } interface MutableBakedMotionDiagnostics extends BakedMotionDiagnostics { attemptedRenditions: string[]; decoderAttempts: BakedMotionDecoderAttemptDiagnostics[]; } export type BakedMotionFetch = (input: string, init?: RequestInit) => Promise; /** Minimal renderer surface used by owned texture uploads. */ export interface BakedMotionRenderer { readonly backend?: unknown; hasFeature?(feature: string): boolean; getDrawingBufferSize?(target: THREE.Vector2): THREE.Vector2; initTexture(texture: THREE.Texture): void; initRenderTarget?(target: THREE.RenderTarget): void; copyTextureToTexture(source: THREE.Texture, destination: THREE.Texture, sourceRegion?: THREE.Box2 | THREE.Box3 | null, destinationPosition?: THREE.Vector2 | THREE.Vector3 | null, sourceLevel?: number, destinationLevel?: number): void; /** Optional render-to-target surface used by the tilt held-composite snapshot lane. */ getRenderTarget?(): THREE.RenderTarget | null; getActiveCubeFace?(): number; getActiveMipmapLevel?(): number; setRenderTarget?(target: THREE.RenderTarget | null, activeCubeFace?: number, activeMipmapLevel?: number): void; render?(scene: THREE.Object3D, camera: THREE.Camera): void | Promise; } export interface BakedMotionOptions { fetchImpl?: BakedMotionFetch; /** Borrowed renderer used by owned texture uploads and the streamed decoded-view GPU ring. */ renderer?: BakedMotionRenderer; /** Borrowed node material; BakedMotion wires its node hooks but never disposes it. */ material?: BakedMotionMaterial; /** Borrowed geometry; omitted geometry is internally created and owned. */ geometry?: BakedMotionGeometry; /** Face the camera via world-up billboarding. Defaults to true only for the owned plane. */ billboard?: boolean; /** Borrowed mesh; its geometry and original material remain caller-owned. */ mesh?: BakedMotionMesh; height?: number; name?: string; playing?: boolean; loop?: boolean; playbackRate?: number; /** Use bounded CPU-backed DataTexture slots instead of VideoFrameTexture uploads. */ cpuUpload?: boolean; /** Use authored depth to reproject between stored views. */ depthReprojection?: boolean; /** * Media delivery policy. The default `'full'` fetches each selected track as one verified * whole-file request. `'segments'` opts in to demand-driven, per-segment verified HTTP * range requests — only for deployments whose servers are known to honor `Range`, and * whose interaction pattern tolerates on-demand fetch latency. */ delivery?: BakedMotionDelivery; /** * Use the package's coarse all-view atlas as evidence for corners that have not decoded yet. * Defaults on when the package ships one. Turn it off to measure against the bare decode path. */ coarseFloor?: boolean; writeDepth?: boolean; hardwareAcceleration?: HardwareAcceleration; /** Maximum wall-clock milliseconds allowed for first decoder output after submission. */ admissionTimeoutMs?: number; /** Automatic ordered fallback, or one exact normalized rendition ID. */ rendition?: 'auto' | string; /** Highest coded rendition width eligible in automatic mode. */ maxRenditionWidth?: number; /** Maximum complete `.af` resource accepted. */ maxFullFileBytes?: number; /** * GPU budget for decoded full-resolution views retained across grid revisits. Defaults to * 128 MiB on desktop and 48 MiB on mobile. Requires a borrowed WebGPU renderer. */ decodedCacheBudget?: number; } export type BakedMotionTimelineParameters = Record & { time: number; }; export type BakedMotionTiltParameters = Record & { tiltX: number; tiltY: number; }; export type BakedMotionRotationParameters = Record & { azimuth: number; elevation?: number; }; export type BakedMotionParameters = BakedMotionTimelineParameters | BakedMotionTiltParameters | BakedMotionRotationParameters; export type BakedMotionViewSource = THREE.Object3D | BakedMotionVector3Like | { position: BakedMotionVector3Like; }; interface BakedMotionAFSource { manifest: AFManifest; } interface BakedMotionSampler { node: TSLVec4Node; alphaNode: TSLFloatNode; sampleColor?: (coordinate: TSLVec2Node) => TSLVec4Node; sampleDepth?: (coordinate: TSLVec2Node) => TSLFloatNode | null; sampleColorSlot?: (slot: number, coordinate: TSLVec2Node) => TSLVec4Node; sampleAlphaSlot?: (slot: number, coordinate: TSLVec2Node) => TSLFloatNode; sampleDepthSlot?: (slot: number, coordinate: TSLVec2Node) => TSLFloatNode | null; blendNode?: TSLFloatNode; weights?: TSLUniformNode<'float', number>[]; presentationWeights?: TSLFloatNode[]; layers?: TSLUniformNode<'int', number>[]; } interface BakedMotionTiltDelta { x: TSLUniformNode<'float', number>; y: TSLUniformNode<'float', number>; sinX: TSLUniformNode<'float', number>; cosX: TSLUniformNode<'float', number>; sinY: TSLUniformNode<'float', number>; cosY: TSLUniformNode<'float', number>; } type BakedMotionGridSample = Extract; interface BakedMotionPendingSeek { sample: BakedMotionGridSample; adaptiveSettled?: boolean; resolve: (value: BakedMotion) => void; reject: (reason?: unknown) => void; promise: Promise; } interface BakedMotionFrameWaiter { generation: number; resolve: (value: BakedMotion) => void; reject: (reason?: unknown) => void; } interface BakedMotionResumeSignal { promise: Promise; resolve(): void; } /** * Load and interact with a Baked Motion package (`manifest.json` + synchronized `.af` tracks). * * ```js * const motion = new BakedMotion( '/models/car.utsbv/manifest.json' ); * await motion.ready; * scene.add( motion.mesh ); * motion.setView( camera ); * await motion.frameReady; // optional deterministic capture after the seek * ``` * * @class BakedMotion * @category Baked Motion */ export declare class BakedMotion { url: string | URL; options: BakedMotionOptions; fetchImpl: BakedMotionFetch; manifest: BakedMotionManifest | null; time: number; playing: boolean; /** Whether timeline playback is holding its last complete frame while the next sample loads. */ get buffering(): boolean; loop: boolean; playbackRate: number; parameters: BakedMotionParameters | null; frameReady: Promise; material: BakedMotionMaterial; mesh: BakedMotionMesh; ready: Promise; state: BakedMotionState; albedo: AFVideo | undefined; depth: AFVideo | null | undefined; _pointerCurrent: BakedMotionTiltParameters; _pointerTarget: BakedMotionTiltParameters; _viewPosition: THREE.Vector3; _clips: AFVideo[]; _encodedSources: AFEncodedSource[]; /** Range-delivered sources for the selected rendition, sampled live by delivery diagnostics. */ _segmentDeliverySources: { albedo: AFRangeSource | null; depth: AFRangeSource | null; }; _trackAspect: number; _rotationFrameDeltas: [TSLUniformNode<'float', number>, TSLUniformNode<'float', number>] | null; _tiltFrameDeltas: BakedMotionTiltDelta[] | null; /** Thumbnail evidence for corners that have not decoded yet; null when the package has none. */ _coarse: BakedMotionCoarseFloor | null; /** * Present decoded views through the package's coarse atlas while their corners are still * decoding. Settable at any time: turning it off pins presentation to the decoded path, which * makes an on-device A/B a property assignment rather than a reload. */ coarseFloor: boolean; _coarseRefreshedAtMs: number | null; private _tiltWarp; private _sampler; private _renderedSinceWire; private _timelineBuffering; private _lastTiltCoordinate; private _depthRangeUniforms; _seekLastIssueMs: number; _seekLastSampleMs: number; _seekPending: BakedMotionPendingSeek | null; _seekTimer: ReturnType | null; _adaptiveVelocity: [number, number]; _adaptiveLastCoordinate: [number, number] | null; _adaptiveFreshCornerLatencyMs: number | null; _adaptiveIncompleteStartedAtMs: number | null; _adaptiveIssueGeneration: number; _frameSetIssueGeneration: number; _decodedCacheBudget: number; _decodedCacheCapacity: number; _disposed: boolean; _abortController: AbortController; _mediaDecoderOwner: string; _renditionAttemptIndex: number; _suspendRequested: boolean; _resumeSignal: BakedMotionResumeSignal | null; _suspendSignal: BakedMotionResumeSignal; _resumePromise: Promise | null; _initialAdmissionComplete: boolean; _diagnostics: MutableBakedMotionDiagnostics; _decoderAttemptHistory: BakedMotionDecoderAttemptDiagnostics[]; _sampleGeneration: number; _presentedGeneration: number; _frameWaiters: BakedMotionFrameWaiter[]; _ownsMaterial: boolean; _ownsGeometry: boolean; constructor(manifestURL: string | URL, options?: BakedMotionOptions); _setState(state: BakedMotionState): void; _assertActive(): void; _refreshDecoderDiagnostics(): void; _fail(error: unknown): never; /** Fold the live per-segment counters of range-delivered tracks into the delivery snapshot. */ _refreshDeliveryDiagnostics(): void; /** Return a deeply immutable, point-in-time diagnostic snapshot. */ getDiagnostics(): Readonly; _disposedOperation(operation: string): never; _trackPresentation(operation: Promise): Promise; _configureMeshGeometry(width: number, height: number): void; _load(): Promise; _releaseAdmissionAttempt(): void; _setupStreaming(albedoSource: BakedMotionAFSource, depthSource: BakedMotionAFSource | null, rendition: BakedMotionIndexedRendition | null): Promise; /** * The presented view as a texture-style node — the map-consumable form of Baked Motion. * Underneath this is the exact official WebCodecs pattern: decoded `VideoFrame`s fed to * `THREE.VideoFrameTexture` slots via `setFrame`, blended by the presentation weights. * Assign it like any node (`material.colorNode = motion.node`); the internally owned * mesh/material facade then never needs to enter the scene. */ get node(): TSLVec4Node; /** {@link BakedMotion#node} at a custom coordinate (`motion.sample( myUv )`). */ sample(coordinate?: TSLVec2Node): TSLVec4Node; /** * Decode the package's coarse atlas, if it ships one. A failure here is never fatal: the floor * is an enhancement, and a package that loses it simply behaves as it did before — so a missing * `createImageBitmap` or an unreachable image must not take the whole clip down with it. */ _loadCoarseFloor(resolveTrack: (file: string) => string, signal: AbortSignal): Promise; /** * Point each floor slot at the corner that slot SHOULD be showing, and mark the slot resident * only when the decoded view for that same corner is on screen. Both sources then carry one * pose, so the dissolve crosses resolution alone. */ _refreshCoarseFloor(): void; _wireMaterial(sampler: BakedMotionSampler, hasAlpha: boolean, sourceWidth?: number): void; _waitForResumeRequest(): Promise; _presentLatestSample(): Promise; _resumeStreamingGroup(): Promise; _presentInitialSample(): Promise; _applyInitialSample(): void; _setParameters(parameters: BakedMotionParameters): this; /** Semantic mass of the new target set already physically presentable in every clip. */ _retainedTargetMass(targetWeights: ReadonlyMap): number; /** Paired presentation mass currently on the streamed slots (mirror of the shader factor). */ _pairedPresentationMass(): number; /** * Route a two-axis streamed sample through either the explicit legacy fixed pacer or the * default decode-time budget controller. The adaptive path retains only the newest sample, * favors the velocity-predicted landing cell, and completes a stopped bilinear set over * successive bounded windows while the forward depth meshes cover the held-view gap. */ _scheduleGridSeek(sample: BakedMotionGridSample, initial: boolean): Promise; _scheduleAdaptiveGridSeek(sample: BakedMotionGridSample, initial: boolean): Promise; _predictAdaptiveGridSample(sample: BakedMotionGridSample): BakedMotionGridSample; _gridSampleNeedsFreshCorner(sample: BakedMotionGridSample): boolean; _adaptiveDecodeBudget(): { capacity: number; measuredMs: number | null; }; _refreshScrubDiagnostics(): void; _markAdaptiveBlendIncomplete(now: number): void; _settleAdaptiveBlend(now?: number): void; _activatePreparedFrameWeights(): void; _issueGridSeek(indices: readonly number[], weights: readonly number[], issuedAt: number): Promise; _issueAdaptiveGridSeek(indices: readonly number[], weights: readonly number[], issuedAt: number): Promise; _prefetchAdaptiveGrid(presentedFrames: readonly number[]): void; _refreshAdaptiveTiltFrameDeltas(): void; _flushPendingSeek(): void; _flushAdaptivePendingSeek(): void; /** Resolve and drop any parked seek — a newer immediate issuance supersedes it. */ _settlePendingSeek(): void; /** Set timeline time in seconds. */ setTime(seconds: number): this; /** Set target pointer in NDC (`[-1,1]`, x-right/y-up). Call update(dt) to ease. */ setPointer(x: number, y: number): this; /** Sample rotation axes from the target→camera direction. */ setView(camera: BakedMotionViewSource): this; update(dt: number): this; /** Park decoder sessions while retaining the last valid texture contents. Idempotent. */ suspend(): this; /** Re-admit decoder sessions and present the latest parameters into retained texture slots. */ resume(): Promise; play(): this; pause(): this; dispose(): void; }