UNPKG

@needle-tools/engine

Version:

Needle Engine is a web-based runtime for 3D apps. It runs on your machine for development with great integrations into editors like Unity or Blender - and can be deployed onto any device! It is flexible, extensible and networking and XR are built-in.

414 lines (413 loc) 20.7 kB
import { AnimationClip, Object3D } from "three"; import type { Light, Material, PerspectiveCamera } from "three"; import type { Animator } from "../Animator.js"; import type { AudioSource } from "../AudioSource.js"; import { type TrackDescriptor, type TrackOptions, type AnimationKeyframe, type Tween, type Vec3Value, type QuatValue, type EulerValue, type ColorValue } from "../AnimationBuilder.js"; /** Keyframe array or tween shorthand */ type KF<V> = AnimationKeyframe<V>[] | Tween<V>; import { SignalReceiver } from "./SignalAsset.js"; import type { PlayableDirector } from "./PlayableDirector.js"; import { ClipExtrapolation } from "./TimelineModels.js"; import type { TimelineAssetModel } from "./TimelineModels.js"; /** * Options for an animation clip in the timeline builder */ export declare type AnimationClipOptions = { /** Start time of the clip in seconds. If omitted, placed after the previous clip on this track. */ start?: number; /** Duration of the clip in seconds. Defaults to the animation clip duration. */ duration?: number; /** Playback speed multiplier (default: 1) */ speed?: number; /** Whether the animation should loop within the clip (default: false) */ loop?: boolean; /** Ease-in duration in seconds (default: 0) */ easeIn?: number; /** Ease-out duration in seconds (default: 0) */ easeOut?: number; /** Offset into the source animation clip in seconds (default: 0) */ clipIn?: number; /** Whether to remove the start offset of the animation (default: false) */ removeStartOffset?: boolean; /** Pre-extrapolation mode (default: None) */ preExtrapolation?: ClipExtrapolation; /** Post-extrapolation mode (default: None) */ postExtrapolation?: ClipExtrapolation; /** Play the clip in reverse */ reversed?: boolean; }; /** * Options for an audio clip in the timeline builder */ export declare type AudioClipOptions = { /** Start time of the clip in seconds. If omitted, placed after the previous clip on this track. */ start?: number; /** Duration of the clip in seconds (required for audio since we can't infer it) */ duration: number; /** Playback speed multiplier (default: 1) */ speed?: number; /** Volume multiplier for this clip (default: 1) */ volume?: number; /** Whether the audio should loop within the clip (default: false) */ loop?: boolean; /** Ease-in duration in seconds (default: 0) */ easeIn?: number; /** Ease-out duration in seconds (default: 0) */ easeOut?: number; }; /** * Options for an activation clip in the timeline builder */ export declare type ActivationClipOptions = { /** Start time of the clip in seconds. If omitted, placed after the previous clip on this track. */ start?: number; /** Duration of the clip in seconds (required) */ duration: number; /** Ease-in duration in seconds (default: 0) */ easeIn?: number; /** Ease-out duration in seconds (default: 0) */ easeOut?: number; }; /** * Options for a control clip in the timeline builder */ export declare type ControlClipOptions = { /** Start time of the clip in seconds. If omitted, placed after the previous clip on this track. */ start?: number; /** Duration of the clip in seconds (required) */ duration: number; /** Whether to control the activation of the source object (default: true) */ controlActivation?: boolean; /** Whether to update a nested PlayableDirector on the source object (default: true) */ updateDirector?: boolean; }; /** * Options for a signal marker in the timeline builder */ export declare type SignalMarkerOptions = { /** Whether the signal should fire if the playback starts past its time (default: false) */ retroActive?: boolean; /** Whether the signal should only fire once (default: false) */ emitOnce?: boolean; }; /** * Shared methods available on all track builders and the TimelineBuilder entry point. * Provides track creation, build, and install methods. * * @category Animation and Sequencing */ export interface TimelineBuilderBase { /** Adds an animation track. Chain `.clip()` or `.track()` to add content. */ animationTrack(name: string, binding?: Animator | Object3D | null): AnimationTrackBuilder; /** Adds an audio track. Chain `.clip()` to add audio clips. */ audioTrack(name: string, binding?: AudioSource | Object3D | null, volume?: number): AudioTrackBuilder; /** Adds an activation track. Chain `.clip()` to define activation windows. */ activationTrack(name: string, binding?: Object3D | null): ActivationTrackBuilder; /** Adds a control track. Chain `.clip()` to control nested objects/timelines. */ controlTrack(name: string): ControlTrackBuilder; /** Adds a signal track. Chain `.signal()` or `.marker()` to add events. */ signalTrack(name: string, binding?: SignalReceiver | Object3D | null): SignalTrackBuilder; /** Adds a marker track. Chain `.marker()` to add markers. */ markerTrack(name: string): MarkerTrackBuilder; /** Builds and returns the {@link TimelineAssetModel}. */ build(): TimelineAssetModel; /** Builds the timeline, assigns it to the director, and wires up signal callbacks. */ install(director: PlayableDirector): TimelineAssetModel; } /** * Builder for animation tracks. * Provides `.clip()` for pre-built AnimationClips and `.track()` for inline animation definition. * * @category Animation and Sequencing */ export interface AnimationTrackBuilder extends TimelineBuilderBase { /** Adds a pre-built AnimationClip */ clip(asset: AnimationClip, options?: AnimationClipOptions): AnimationTrackBuilder; /** Adds a clip from a single {@link TrackDescriptor} */ clip(descriptor: TrackDescriptor, options?: AnimationClipOptions): AnimationTrackBuilder; /** Adds a clip from multiple {@link TrackDescriptor}s */ clip(descriptors: TrackDescriptor[], options?: AnimationClipOptions): AnimationTrackBuilder; /** Adds an animation track for an Object3D's position or scale */ track(target: Object3D, property: "position" | "scale", keyframes: KF<Vec3Value>, options?: TrackOptions): AnimationTrackBuilder; /** Adds an animation track for an Object3D's quaternion */ track(target: Object3D, property: "quaternion", keyframes: KF<QuatValue>, options?: TrackOptions): AnimationTrackBuilder; /** Adds an animation track for an Object3D's rotation (Euler, converted to quaternion) */ track(target: Object3D, property: "rotation", keyframes: KF<EulerValue>, options?: TrackOptions): AnimationTrackBuilder; /** Adds an animation track for an Object3D's visibility */ track(target: Object3D, property: "visible", keyframes: KF<boolean>, options?: TrackOptions): AnimationTrackBuilder; /** Adds an animation track for a material's numeric property */ track(target: Material, property: "opacity" | "roughness" | "metalness" | "alphaTest" | "emissiveIntensity" | "envMapIntensity" | "bumpScale" | "displacementScale" | "displacementBias", keyframes: KF<number>, options?: TrackOptions): AnimationTrackBuilder; /** Adds an animation track for a material's color property */ track(target: Material, property: "color" | "emissive", keyframes: KF<ColorValue>, options?: TrackOptions): AnimationTrackBuilder; /** Adds an animation track for a light's numeric property */ track(target: Light, property: "intensity" | "distance" | "angle" | "penumbra" | "decay", keyframes: KF<number>, options?: TrackOptions): AnimationTrackBuilder; /** Adds an animation track for a light's color */ track(target: Light, property: "color", keyframes: KF<ColorValue>, options?: TrackOptions): AnimationTrackBuilder; /** Adds an animation track for a camera's numeric property */ track(target: PerspectiveCamera, property: "fov" | "near" | "far" | "zoom", keyframes: KF<number>, options?: TrackOptions): AnimationTrackBuilder; /** Mutes this track so it is skipped during playback */ muted(muted?: boolean): AnimationTrackBuilder; } /** * Builder for audio tracks. Provides `.clip()` for adding audio clips by URL. * @category Animation and Sequencing */ export interface AudioTrackBuilder extends TimelineBuilderBase { /** Adds an audio clip by URL */ clip(url: string, options: AudioClipOptions): AudioTrackBuilder; /** Mutes this track so it is skipped during playback */ muted(muted?: boolean): AudioTrackBuilder; } /** * Builder for activation tracks. Provides `.clip()` for defining activation windows. * @category Animation and Sequencing */ export interface ActivationTrackBuilder extends TimelineBuilderBase { /** Adds an activation clip that shows/hides the bound object */ clip(options: ActivationClipOptions): ActivationTrackBuilder; /** Mutes this track so it is skipped during playback */ muted(muted?: boolean): ActivationTrackBuilder; } /** * Builder for control tracks. Provides `.clip()` for controlling nested objects/timelines. * @category Animation and Sequencing */ export interface ControlTrackBuilder extends TimelineBuilderBase { /** Adds a control clip for a source object */ clip(sourceObject: Object3D, options: ControlClipOptions): ControlTrackBuilder; /** Mutes this track so it is skipped during playback */ muted(muted?: boolean): ControlTrackBuilder; } /** * Builder for signal tracks. Provides `.signal()` for callback-based signals and `.marker()` for asset-based markers. * @category Animation and Sequencing */ export interface SignalTrackBuilder extends TimelineBuilderBase { /** Adds a signal with a callback that fires at the given time */ signal(time: number, callback: Function, options?: SignalMarkerOptions): SignalTrackBuilder; /** Adds a signal marker referencing a signal asset by guid */ marker(time: number, asset: string, options?: SignalMarkerOptions): SignalTrackBuilder; /** Mutes this track so it is skipped during playback */ muted(muted?: boolean): SignalTrackBuilder; } /** * Builder for marker tracks. Provides `.marker()` for adding markers. * @category Animation and Sequencing */ export interface MarkerTrackBuilder extends TimelineBuilderBase { /** Adds a marker referencing a signal asset by guid */ marker(time: number, asset: string, options?: SignalMarkerOptions): MarkerTrackBuilder; /** Mutes this track so it is skipped during playback */ muted(muted?: boolean): MarkerTrackBuilder; } /** * A fluent builder for creating timeline assets ({@link TimelineAssetModel}) from code. * * Use {@link TimelineBuilder.create} to start building a timeline. * * @example Using build() for timelines without signal callbacks * ```ts * const timeline = TimelineBuilder.create("MySequence") * .animationTrack("Character", animator) * .clip(walkClip, { duration: 2, easeIn: 0.3 }) * .clip(runClip, { duration: 3, easeIn: 0.5, easeOut: 0.5 }) * .activationTrack("FX", particleObject) * .clip({ start: 1, duration: 2 }) * .audioTrack("Music", audioSource) * .clip("music.mp3", { start: 0, duration: 5, volume: 0.8 }) * .build(); * * director.playableAsset = timeline; * director.play(); * ``` * * @example With inline tracks (no pre-built clips needed) * ```ts * TimelineBuilder.create("DoorSequence") * .animationTrack("Door", door) * .track(door, "position", { from: [0, 0, 0], to: [2, 0, 0], duration: 1 }) * .track(light, "intensity", { from: 0, to: 5, duration: 1 }) * .signalTrack("Events") * .signal(0.5, () => playSound("creak")) * .install(director); * * director.play(); * ``` * * @example Using install() with signal callbacks * ```ts * TimelineBuilder.create("WithSignals") * .animationTrack("Character", animator) * .clip(walkClip, { duration: 2 }) * .signalTrack("Events") * .signal(1.0, () => console.log("1 second!")) * .signal(2.0, () => spawnParticles()) * .install(director); * * director.play(); * ``` * * @category Animation and Sequencing * @group Utilities */ export declare class TimelineBuilder { private _name; private _tracks; private _currentTrack; private _pendingSignals; private _idProvider; private constructor(); /** * Creates a new TimelineBuilder instance. * @param name - Name for the timeline asset * @param seed - Optional numeric seed for deterministic guid generation. Defaults to `Date.now()`. */ static create(name?: string, seed?: number): TimelineBuilderBase; /** * Adds an animation track. Chain `.clip()` calls to add pre-built clips, * or chain `.track()` calls to define animation data inline: * * @example With pre-built AnimationClip * ```ts * .animationTrack("Character", animator) * .clip(walkClip, { duration: 2, easeIn: 0.3 }) * .clip(runClip, { duration: 3 }) * ``` * * @example With inline tracks * ```ts * .animationTrack("Door", door) * .track(door, "position", { from: [0, 0, 0], to: [2, 0, 0], duration: 1 }) * .track(light, "intensity", { from: 0, to: 5, duration: 1 }) * ``` * * @param name - Display name for the track * @param binding - The Animator or Object3D to animate */ animationTrack(name: string, binding?: Animator | Object3D | null): AnimationTrackBuilder; /** * Adds an audio track. Subsequent `.clip()` calls add audio clips to this track. * @param name - Display name for the track * @param binding - The AudioSource to play audio on (optional) * @param volume - Track volume multiplier (default: 1) */ audioTrack(name: string, binding?: AudioSource | Object3D | null, volume?: number): AudioTrackBuilder; /** * Adds an activation track. Subsequent `.clip()` calls define when the bound object is active. * @param name - Display name for the track * @param binding - The Object3D to show/hide */ activationTrack(name: string, binding?: Object3D | null): ActivationTrackBuilder; /** * Adds a control track. Subsequent `.clip()` calls control nested timelines or objects. * @param name - Display name for the track */ controlTrack(name: string): ControlTrackBuilder; /** * Adds a signal track. Use `.signal()` or `.marker()` to add signal markers. * @param name - Display name for the track * @param binding - The SignalReceiver component (optional — if using `.signal()` with callbacks, one is created automatically by {@link install}) */ signalTrack(name: string, binding?: SignalReceiver | Object3D | null): SignalTrackBuilder; /** * Adds a marker track. Use `.marker()` to add markers. * @param name - Display name for the track */ markerTrack(name: string): MarkerTrackBuilder; /** * Adds a clip to the current track. The clip type must match the track type. * * - On an **animation track**: pass an `AnimationClip`, a {@link TrackDescriptor}, or a `TrackDescriptor[]` — and optional {@link AnimationClipOptions} * - On an **audio track**: pass a clip URL (string) and {@link AudioClipOptions} * - On an **activation track**: pass {@link ActivationClipOptions} * - On a **control track**: pass an Object3D and {@link ControlClipOptions} */ clip(asset: AnimationClip, options?: AnimationClipOptions): this; clip(descriptor: TrackDescriptor, options?: AnimationClipOptions): this; clip(descriptors: TrackDescriptor[], options?: AnimationClipOptions): this; clip(url: string, options: AudioClipOptions): this; clip(options: ActivationClipOptions): this; clip(sourceObject: Object3D, options: ControlClipOptions): this; /** * Adds a signal marker to the current signal or marker track. * @param time - Time in seconds when the signal fires * @param asset - The signal asset identifier (guid string) * @param options - Optional marker configuration */ marker(time: number, asset: string, options?: SignalMarkerOptions): this; /** * Adds a signal with a callback to the current signal track. * This is a convenience method that automatically generates a signal asset guid, * adds the marker, and registers the callback so that {@link install} can wire up * the `SignalReceiver` on the director's GameObject. * * @param time - Time in seconds when the signal fires * @param callback - The function to invoke when the signal fires * @param options - Optional marker configuration * * @example * ```ts * const timeline = TimelineBuilder.create("Sequence") * .signalTrack("Events") * .signal(1.0, () => console.log("1 second reached!")) * .signal(3.5, () => console.log("halfway!"), { emitOnce: true }) * .install(director); * ``` */ signal(time: number, callback: Function, options?: SignalMarkerOptions): this; /** * Mutes the current track so it is skipped during playback. */ muted(muted?: boolean): this; /** Adds an animation track descriptor for an Object3D's position or scale to the current animation track */ track(target: Object3D, property: "position" | "scale", keyframes: KF<Vec3Value>, options?: TrackOptions): this; /** Adds an animation track descriptor for an Object3D's quaternion to the current animation track */ track(target: Object3D, property: "quaternion", keyframes: KF<QuatValue>, options?: TrackOptions): this; /** Adds an animation track descriptor for an Object3D's rotation (Euler, converted to quaternion) to the current animation track */ track(target: Object3D, property: "rotation", keyframes: KF<EulerValue>, options?: TrackOptions): this; /** Adds an animation track descriptor for an Object3D's visibility to the current animation track */ track(target: Object3D, property: "visible", keyframes: KF<boolean>, options?: TrackOptions): this; /** Adds an animation track descriptor for a material's numeric property to the current animation track */ track(target: Material, property: "opacity" | "roughness" | "metalness" | "alphaTest" | "emissiveIntensity" | "envMapIntensity" | "bumpScale" | "displacementScale" | "displacementBias", keyframes: KF<number>, options?: TrackOptions): this; /** Adds an animation track descriptor for a material's color property to the current animation track */ track(target: Material, property: "color" | "emissive", keyframes: KF<ColorValue>, options?: TrackOptions): this; /** Adds an animation track descriptor for a light's numeric property to the current animation track */ track(target: Light, property: "intensity" | "distance" | "angle" | "penumbra" | "decay", keyframes: KF<number>, options?: TrackOptions): this; /** Adds an animation track descriptor for a light's color to the current animation track */ track(target: Light, property: "color", keyframes: KF<ColorValue>, options?: TrackOptions): this; /** Adds an animation track descriptor for a camera's numeric property to the current animation track */ track(target: PerspectiveCamera, property: "fov" | "near" | "far" | "zoom", keyframes: KF<number>, options?: TrackOptions): this; /** * Builds and returns the {@link TimelineAssetModel}. * Assign the result to `PlayableDirector.playableAsset` to play it. * * If you used `.signal()` with callbacks, use {@link install} instead — it calls `build()` * internally and also wires up the SignalReceiver on the director's GameObject. */ build(): TimelineAssetModel; /** * Builds the timeline asset, assigns it to the director, and wires up any * `.signal()` callbacks by creating/configuring a {@link SignalReceiver} on the * director's GameObject. * * @param director - The PlayableDirector to install the timeline on * @returns The built TimelineAssetModel (also assigned to `director.playableAsset`) * * @example * ```ts * TimelineBuilder.create("MyTimeline") * .animationTrack("Anim", animator) * .clip(walkClip, { duration: 2 }) * .signalTrack("Events") * .signal(1.0, () => console.log("signal fired!")) * .install(director); * * director.play(); * ``` */ install(director: PlayableDirector): TimelineAssetModel; private pushTrack; /** Commits any pending `.track()` descriptors on the current animation track into a clip */ private commitInlineTracks; } export {};