@needle-tools/engine 5.1.0-alpha.3 → 5.1.0-alpha.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +68 -0
- package/SKILL.md +4 -1
- package/components.needle.json +1 -1
- package/dist/{needle-engine.bundle-DF01sSGQ.js → needle-engine.bundle-C-LG00ZZ.js} +10681 -10100
- package/dist/needle-engine.bundle-D7tzaiYE.min.js +1733 -0
- package/dist/{needle-engine.bundle-C-ixARur.umd.cjs → needle-engine.bundle-OPkPmdUM.umd.cjs} +161 -161
- package/dist/needle-engine.d.ts +1349 -317
- package/dist/needle-engine.js +556 -555
- package/dist/needle-engine.min.js +1 -1
- package/dist/needle-engine.umd.cjs +1 -1
- package/dist/three.js +1 -0
- package/dist/three.min.js +21 -21
- package/dist/three.umd.cjs +16 -16
- package/lib/engine/api.d.ts +5 -0
- package/lib/engine/api.js +4 -0
- package/lib/engine/api.js.map +1 -1
- package/lib/engine/codegen/register_types.js +10 -18
- package/lib/engine/codegen/register_types.js.map +1 -1
- package/lib/engine/engine_camera.fit.js +16 -4
- package/lib/engine/engine_camera.fit.js.map +1 -1
- package/lib/engine/engine_context.d.ts +20 -7
- package/lib/engine/engine_context.js +31 -15
- package/lib/engine/engine_context.js.map +1 -1
- package/lib/engine/engine_context_eventbus.d.ts +47 -0
- package/lib/engine/engine_context_eventbus.js +47 -0
- package/lib/engine/engine_context_eventbus.js.map +1 -0
- package/lib/engine/engine_disposable.d.ts +172 -0
- package/lib/engine/engine_disposable.js +136 -0
- package/lib/engine/engine_disposable.js.map +1 -0
- package/lib/engine/engine_gameobject.d.ts +1 -10
- package/lib/engine/engine_gameobject.js +20 -118
- package/lib/engine/engine_gameobject.js.map +1 -1
- package/lib/engine/engine_gltf_builtin_components.js +7 -69
- package/lib/engine/engine_gltf_builtin_components.js.map +1 -1
- package/lib/engine/engine_input.d.ts +23 -4
- package/lib/engine/engine_input.js +2 -1
- package/lib/engine/engine_input.js.map +1 -1
- package/lib/engine/engine_instantiate_resolve.d.ts +42 -0
- package/lib/engine/engine_instantiate_resolve.js +372 -0
- package/lib/engine/engine_instantiate_resolve.js.map +1 -0
- package/lib/engine/engine_mainloop_utils.js +2 -2
- package/lib/engine/engine_mainloop_utils.js.map +1 -1
- package/lib/engine/engine_networking.d.ts +51 -37
- package/lib/engine/engine_networking.js +132 -82
- package/lib/engine/engine_networking.js.map +1 -1
- package/lib/engine/engine_networking.transport.websocket.d.ts +15 -0
- package/lib/engine/engine_networking.transport.websocket.js +38 -0
- package/lib/engine/engine_networking.transport.websocket.js.map +1 -0
- package/lib/engine/engine_networking_instantiate.js +2 -2
- package/lib/engine/engine_networking_instantiate.js.map +1 -1
- package/lib/engine/engine_networking_types.d.ts +39 -1
- package/lib/engine/engine_networking_types.js +7 -0
- package/lib/engine/engine_networking_types.js.map +1 -1
- package/lib/engine/engine_physics_rapier.d.ts +21 -3
- package/lib/engine/engine_physics_rapier.js +94 -25
- package/lib/engine/engine_physics_rapier.js.map +1 -1
- package/lib/engine/engine_serialization_builtin_serializer.js +1 -5
- package/lib/engine/engine_serialization_builtin_serializer.js.map +1 -1
- package/lib/engine/engine_serialization_core.d.ts +1 -0
- package/lib/engine/engine_serialization_core.js +7 -0
- package/lib/engine/engine_serialization_core.js.map +1 -1
- package/lib/engine/engine_types.d.ts +29 -11
- package/lib/engine/engine_types.js +1 -1
- package/lib/engine/engine_types.js.map +1 -1
- package/lib/engine/engine_util_decorator.js +7 -2
- package/lib/engine/engine_util_decorator.js.map +1 -1
- package/lib/engine/engine_utils.d.ts +1 -1
- package/lib/engine/engine_utils.js +19 -5
- package/lib/engine/engine_utils.js.map +1 -1
- package/lib/engine-components/AnimationBuilder.d.ts +158 -0
- package/lib/engine-components/AnimationBuilder.js +305 -0
- package/lib/engine-components/AnimationBuilder.js.map +1 -0
- package/lib/engine-components/Animator.d.ts +6 -0
- package/lib/engine-components/Animator.js +23 -13
- package/lib/engine-components/Animator.js.map +1 -1
- package/lib/engine-components/AnimatorController.builder.d.ts +191 -0
- package/lib/engine-components/AnimatorController.builder.js +263 -0
- package/lib/engine-components/AnimatorController.builder.js.map +1 -0
- package/lib/engine-components/AnimatorController.d.ts +2 -119
- package/lib/engine-components/AnimatorController.js +33 -232
- package/lib/engine-components/AnimatorController.js.map +1 -1
- package/lib/engine-components/Collider.d.ts +18 -9
- package/lib/engine-components/Collider.js +61 -14
- package/lib/engine-components/Collider.js.map +1 -1
- package/lib/engine-components/Component.d.ts +72 -9
- package/lib/engine-components/Component.js +114 -10
- package/lib/engine-components/Component.js.map +1 -1
- package/lib/engine-components/ContactShadows.d.ts +1 -0
- package/lib/engine-components/ContactShadows.js +14 -1
- package/lib/engine-components/ContactShadows.js.map +1 -1
- package/lib/engine-components/DragControls.js +0 -7
- package/lib/engine-components/DragControls.js.map +1 -1
- package/lib/engine-components/DropListener.js +3 -0
- package/lib/engine-components/DropListener.js.map +1 -1
- package/lib/engine-components/EventList.d.ts +31 -9
- package/lib/engine-components/EventList.js +37 -76
- package/lib/engine-components/EventList.js.map +1 -1
- package/lib/engine-components/Joints.d.ts +4 -2
- package/lib/engine-components/Joints.js +19 -3
- package/lib/engine-components/Joints.js.map +1 -1
- package/lib/engine-components/Light.js +9 -1
- package/lib/engine-components/Light.js.map +1 -1
- package/lib/engine-components/OrbitControls.d.ts +0 -2
- package/lib/engine-components/OrbitControls.js +14 -1
- package/lib/engine-components/OrbitControls.js.map +1 -1
- package/lib/engine-components/RigidBody.d.ts +12 -4
- package/lib/engine-components/RigidBody.js +18 -4
- package/lib/engine-components/RigidBody.js.map +1 -1
- package/lib/engine-components/SceneSwitcher.js +3 -0
- package/lib/engine-components/SceneSwitcher.js.map +1 -1
- package/lib/engine-components/api.d.ts +2 -1
- package/lib/engine-components/api.js +2 -1
- package/lib/engine-components/api.js.map +1 -1
- package/lib/engine-components/codegen/components.d.ts +7 -13
- package/lib/engine-components/codegen/components.js +7 -13
- package/lib/engine-components/codegen/components.js.map +1 -1
- package/lib/engine-components/postprocessing/Effects/Tonemapping.utils.d.ts +1 -1
- package/lib/engine-components/timeline/PlayableDirector.d.ts +21 -11
- package/lib/engine-components/timeline/PlayableDirector.js +75 -67
- package/lib/engine-components/timeline/PlayableDirector.js.map +1 -1
- package/lib/engine-components/timeline/SignalAsset.d.ts +3 -1
- package/lib/engine-components/timeline/SignalAsset.js +1 -0
- package/lib/engine-components/timeline/SignalAsset.js.map +1 -1
- package/lib/engine-components/timeline/TimelineBuilder.d.ts +413 -0
- package/lib/engine-components/timeline/TimelineBuilder.js +506 -0
- package/lib/engine-components/timeline/TimelineBuilder.js.map +1 -0
- package/lib/engine-components/timeline/TimelineModels.d.ts +2 -1
- package/lib/engine-components/timeline/TimelineModels.js +3 -0
- package/lib/engine-components/timeline/TimelineModels.js.map +1 -1
- package/lib/engine-components/timeline/TimelineTracks.d.ts +37 -6
- package/lib/engine-components/timeline/TimelineTracks.js +92 -26
- package/lib/engine-components/timeline/TimelineTracks.js.map +1 -1
- package/lib/engine-components/timeline/index.d.ts +2 -1
- package/lib/engine-components/timeline/index.js +2 -0
- package/lib/engine-components/timeline/index.js.map +1 -1
- package/lib/engine-components/web/CursorFollow.d.ts +0 -1
- package/lib/engine-components/web/CursorFollow.js +0 -1
- package/lib/engine-components/web/CursorFollow.js.map +1 -1
- package/package.json +2 -83
- package/plugins/common/cloud.js +6 -1
- package/plugins/common/license.js +5 -2
- package/plugins/common/worker.js +9 -4
- package/plugins/vite/dependencies.js +1 -11
- package/plugins/vite/dependency-watcher.js +2 -2
- package/plugins/vite/editor-connection.js +3 -3
- package/plugins/vite/license.js +19 -1
- package/plugins/vite/reload.js +1 -1
- package/plugins/vite/server.js +2 -1
- package/src/engine/api.ts +7 -0
- package/src/engine/codegen/register_types.ts +10 -18
- package/src/engine/engine_camera.fit.ts +15 -4
- package/src/engine/engine_context.ts +32 -16
- package/src/engine/engine_context_eventbus.ts +73 -0
- package/src/engine/engine_disposable.ts +214 -0
- package/src/engine/engine_gameobject.ts +52 -157
- package/src/engine/engine_gltf_builtin_components.ts +7 -76
- package/src/engine/engine_input.ts +27 -6
- package/src/engine/engine_instantiate_resolve.ts +407 -0
- package/src/engine/engine_mainloop_utils.ts +2 -2
- package/src/engine/engine_networking.transport.websocket.ts +45 -0
- package/src/engine/engine_networking.ts +161 -137
- package/src/engine/engine_networking_instantiate.ts +2 -2
- package/src/engine/engine_networking_types.ts +41 -1
- package/src/engine/engine_physics_rapier.ts +102 -33
- package/src/engine/engine_serialization_builtin_serializer.ts +1 -6
- package/src/engine/engine_serialization_core.ts +9 -0
- package/src/engine/engine_types.ts +46 -27
- package/src/engine/engine_util_decorator.ts +7 -2
- package/src/engine/engine_utils.ts +16 -5
- package/src/engine-components/AnimationBuilder.ts +472 -0
- package/src/engine-components/Animator.ts +24 -12
- package/src/engine-components/AnimatorController.builder.ts +387 -0
- package/src/engine-components/AnimatorController.ts +20 -291
- package/src/engine-components/Collider.ts +66 -18
- package/src/engine-components/Component.ts +118 -20
- package/src/engine-components/ContactShadows.ts +15 -1
- package/src/engine-components/DragControls.ts +0 -9
- package/src/engine-components/DropListener.ts +3 -0
- package/src/engine-components/EventList.ts +45 -83
- package/src/engine-components/Joints.ts +20 -4
- package/src/engine-components/Light.ts +10 -2
- package/src/engine-components/OrbitControls.ts +16 -5
- package/src/engine-components/RigidBody.ts +18 -4
- package/src/engine-components/SceneSwitcher.ts +3 -0
- package/src/engine-components/api.ts +2 -1
- package/src/engine-components/codegen/components.ts +7 -13
- package/src/engine-components/timeline/PlayableDirector.ts +83 -81
- package/src/engine-components/timeline/SignalAsset.ts +4 -1
- package/src/engine-components/timeline/TimelineBuilder.ts +824 -0
- package/src/engine-components/timeline/TimelineModels.ts +5 -1
- package/src/engine-components/timeline/TimelineTracks.ts +96 -27
- package/src/engine-components/timeline/index.ts +2 -1
- package/src/engine-components/web/CursorFollow.ts +0 -1
- package/dist/needle-engine.bundle-CHmXdnE1.min.js +0 -1733
- package/lib/engine-components/AvatarLoader.d.ts +0 -80
- package/lib/engine-components/AvatarLoader.js +0 -232
- package/lib/engine-components/AvatarLoader.js.map +0 -1
- package/lib/engine-components/avatar/AvatarBlink_Simple.d.ts +0 -11
- package/lib/engine-components/avatar/AvatarBlink_Simple.js +0 -77
- package/lib/engine-components/avatar/AvatarBlink_Simple.js.map +0 -1
- package/lib/engine-components/avatar/AvatarEyeLook_Rotation.d.ts +0 -14
- package/lib/engine-components/avatar/AvatarEyeLook_Rotation.js +0 -69
- package/lib/engine-components/avatar/AvatarEyeLook_Rotation.js.map +0 -1
- package/lib/engine-components/avatar/Avatar_Brain_LookAt.d.ts +0 -29
- package/lib/engine-components/avatar/Avatar_Brain_LookAt.js +0 -122
- package/lib/engine-components/avatar/Avatar_Brain_LookAt.js.map +0 -1
- package/lib/engine-components/avatar/Avatar_MouthShapes.d.ts +0 -15
- package/lib/engine-components/avatar/Avatar_MouthShapes.js +0 -80
- package/lib/engine-components/avatar/Avatar_MouthShapes.js.map +0 -1
- package/lib/engine-components/avatar/Avatar_MustacheShake.d.ts +0 -9
- package/lib/engine-components/avatar/Avatar_MustacheShake.js +0 -30
- package/lib/engine-components/avatar/Avatar_MustacheShake.js.map +0 -1
- package/src/engine-components/AvatarLoader.ts +0 -264
- package/src/engine-components/avatar/AvatarBlink_Simple.ts +0 -70
- package/src/engine-components/avatar/AvatarEyeLook_Rotation.ts +0 -64
- package/src/engine-components/avatar/Avatar_Brain_LookAt.ts +0 -140
- package/src/engine-components/avatar/Avatar_MouthShapes.ts +0 -84
- package/src/engine-components/avatar/Avatar_MustacheShake.ts +0 -32
|
@@ -0,0 +1,824 @@
|
|
|
1
|
+
import { AnimationClip, Object3D } from "three";
|
|
2
|
+
import type { Light, Material, PerspectiveCamera } from "three";
|
|
3
|
+
|
|
4
|
+
import type { Animator } from "../Animator.js";
|
|
5
|
+
import type { AudioSource } from "../AudioSource.js";
|
|
6
|
+
import { GameObject } from "../Component.js";
|
|
7
|
+
import { InstantiateIdProvider } from "../../engine/engine_networking_instantiate.js";
|
|
8
|
+
import { EventList } from "../EventList.js";
|
|
9
|
+
import { isTrackDescriptor, resolveToClip, track as trackFn, type TrackDescriptor, type TrackOptions, type AnimationKeyframe, type Tween, type Vec3Value, type QuatValue, type EulerValue, type ColorValue } from "../AnimationBuilder.js";
|
|
10
|
+
|
|
11
|
+
/** Keyframe array or tween shorthand */
|
|
12
|
+
type KF<V> = AnimationKeyframe<V>[] | Tween<V>;
|
|
13
|
+
import { SignalAsset, SignalReceiver, SignalReceiverEvent } from "./SignalAsset.js";
|
|
14
|
+
import type { PlayableDirector } from "./PlayableDirector.js";
|
|
15
|
+
import { ClipExtrapolation, TrackType } from "./TimelineModels.js";
|
|
16
|
+
import type { TimelineAssetModel, TrackModel, ClipModel, AnimationClipModel, AudioClipModel, ControlClipModel, MarkerModel, SignalMarkerModel, TrackOffset } from "./TimelineModels.js";
|
|
17
|
+
import { MarkerType } from "./TimelineModels.js";
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Options for an animation clip in the timeline builder
|
|
21
|
+
*/
|
|
22
|
+
export declare type AnimationClipOptions = {
|
|
23
|
+
/** Start time of the clip in seconds. If omitted, placed after the previous clip on this track. */
|
|
24
|
+
start?: number;
|
|
25
|
+
/** Duration of the clip in seconds. Defaults to the animation clip duration. */
|
|
26
|
+
duration?: number;
|
|
27
|
+
/** Playback speed multiplier (default: 1) */
|
|
28
|
+
speed?: number;
|
|
29
|
+
/** Whether the animation should loop within the clip (default: false) */
|
|
30
|
+
loop?: boolean;
|
|
31
|
+
/** Ease-in duration in seconds (default: 0) */
|
|
32
|
+
easeIn?: number;
|
|
33
|
+
/** Ease-out duration in seconds (default: 0) */
|
|
34
|
+
easeOut?: number;
|
|
35
|
+
/** Offset into the source animation clip in seconds (default: 0) */
|
|
36
|
+
clipIn?: number;
|
|
37
|
+
/** Whether to remove the start offset of the animation (default: false) */
|
|
38
|
+
removeStartOffset?: boolean;
|
|
39
|
+
/** Pre-extrapolation mode (default: None) */
|
|
40
|
+
preExtrapolation?: ClipExtrapolation;
|
|
41
|
+
/** Post-extrapolation mode (default: None) */
|
|
42
|
+
postExtrapolation?: ClipExtrapolation;
|
|
43
|
+
/** Play the clip in reverse */
|
|
44
|
+
reversed?: boolean;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Options for an audio clip in the timeline builder
|
|
49
|
+
*/
|
|
50
|
+
export declare type AudioClipOptions = {
|
|
51
|
+
/** Start time of the clip in seconds. If omitted, placed after the previous clip on this track. */
|
|
52
|
+
start?: number;
|
|
53
|
+
/** Duration of the clip in seconds (required for audio since we can't infer it) */
|
|
54
|
+
duration: number;
|
|
55
|
+
/** Playback speed multiplier (default: 1) */
|
|
56
|
+
speed?: number;
|
|
57
|
+
/** Volume multiplier for this clip (default: 1) */
|
|
58
|
+
volume?: number;
|
|
59
|
+
/** Whether the audio should loop within the clip (default: false) */
|
|
60
|
+
loop?: boolean;
|
|
61
|
+
/** Ease-in duration in seconds (default: 0) */
|
|
62
|
+
easeIn?: number;
|
|
63
|
+
/** Ease-out duration in seconds (default: 0) */
|
|
64
|
+
easeOut?: number;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Options for an activation clip in the timeline builder
|
|
69
|
+
*/
|
|
70
|
+
export declare type ActivationClipOptions = {
|
|
71
|
+
/** Start time of the clip in seconds. If omitted, placed after the previous clip on this track. */
|
|
72
|
+
start?: number;
|
|
73
|
+
/** Duration of the clip in seconds (required) */
|
|
74
|
+
duration: number;
|
|
75
|
+
/** Ease-in duration in seconds (default: 0) */
|
|
76
|
+
easeIn?: number;
|
|
77
|
+
/** Ease-out duration in seconds (default: 0) */
|
|
78
|
+
easeOut?: number;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Options for a control clip in the timeline builder
|
|
83
|
+
*/
|
|
84
|
+
export declare type ControlClipOptions = {
|
|
85
|
+
/** Start time of the clip in seconds. If omitted, placed after the previous clip on this track. */
|
|
86
|
+
start?: number;
|
|
87
|
+
/** Duration of the clip in seconds (required) */
|
|
88
|
+
duration: number;
|
|
89
|
+
/** Whether to control the activation of the source object (default: true) */
|
|
90
|
+
controlActivation?: boolean;
|
|
91
|
+
/** Whether to update a nested PlayableDirector on the source object (default: true) */
|
|
92
|
+
updateDirector?: boolean;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* Options for a signal marker in the timeline builder
|
|
97
|
+
*/
|
|
98
|
+
export declare type SignalMarkerOptions = {
|
|
99
|
+
/** Whether the signal should fire if the playback starts past its time (default: false) */
|
|
100
|
+
retroActive?: boolean;
|
|
101
|
+
/** Whether the signal should only fire once (default: false) */
|
|
102
|
+
emitOnce?: boolean;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
// Internal types for track building
|
|
106
|
+
type BuilderTrack = {
|
|
107
|
+
name: string;
|
|
108
|
+
type: TrackType;
|
|
109
|
+
muted: boolean;
|
|
110
|
+
outputs: Array<null | object>;
|
|
111
|
+
clips: ClipModel[];
|
|
112
|
+
markers: MarkerModel[];
|
|
113
|
+
volume?: number;
|
|
114
|
+
trackOffset?: TrackOffset;
|
|
115
|
+
cursor: number; // current time position for auto-advancing
|
|
116
|
+
inlineTracks: TrackDescriptor[]; // accumulated by .track() calls, committed at boundaries
|
|
117
|
+
};
|
|
118
|
+
|
|
119
|
+
type PendingSignal = {
|
|
120
|
+
trackIndex: number;
|
|
121
|
+
guid: string;
|
|
122
|
+
callback: Function;
|
|
123
|
+
};
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
// ============================================================
|
|
127
|
+
// Track builder interfaces — typed views per track type
|
|
128
|
+
// ============================================================
|
|
129
|
+
|
|
130
|
+
/**
|
|
131
|
+
* Shared methods available on all track builders and the TimelineBuilder entry point.
|
|
132
|
+
* Provides track creation, build, and install methods.
|
|
133
|
+
*
|
|
134
|
+
* @category Animation and Sequencing
|
|
135
|
+
*/
|
|
136
|
+
export interface TimelineBuilderBase {
|
|
137
|
+
/** Adds an animation track. Chain `.clip()` or `.track()` to add content. */
|
|
138
|
+
animationTrack(name: string, binding?: Animator | Object3D | null): AnimationTrackBuilder;
|
|
139
|
+
/** Adds an audio track. Chain `.clip()` to add audio clips. */
|
|
140
|
+
audioTrack(name: string, binding?: AudioSource | Object3D | null, volume?: number): AudioTrackBuilder;
|
|
141
|
+
/** Adds an activation track. Chain `.clip()` to define activation windows. */
|
|
142
|
+
activationTrack(name: string, binding?: Object3D | null): ActivationTrackBuilder;
|
|
143
|
+
/** Adds a control track. Chain `.clip()` to control nested objects/timelines. */
|
|
144
|
+
controlTrack(name: string): ControlTrackBuilder;
|
|
145
|
+
/** Adds a signal track. Chain `.signal()` or `.marker()` to add events. */
|
|
146
|
+
signalTrack(name: string, binding?: SignalReceiver | Object3D | null): SignalTrackBuilder;
|
|
147
|
+
/** Adds a marker track. Chain `.marker()` to add markers. */
|
|
148
|
+
markerTrack(name: string): MarkerTrackBuilder;
|
|
149
|
+
/** Builds and returns the {@link TimelineAssetModel}. */
|
|
150
|
+
build(): TimelineAssetModel;
|
|
151
|
+
/** Builds the timeline, assigns it to the director, and wires up signal callbacks. */
|
|
152
|
+
install(director: PlayableDirector): TimelineAssetModel;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
/**
|
|
156
|
+
* Builder for animation tracks.
|
|
157
|
+
* Provides `.clip()` for pre-built AnimationClips and `.track()` for inline animation definition.
|
|
158
|
+
*
|
|
159
|
+
* @category Animation and Sequencing
|
|
160
|
+
*/
|
|
161
|
+
export interface AnimationTrackBuilder extends TimelineBuilderBase {
|
|
162
|
+
/** Adds a pre-built AnimationClip */
|
|
163
|
+
clip(asset: AnimationClip, options?: AnimationClipOptions): AnimationTrackBuilder;
|
|
164
|
+
/** Adds a clip from a single {@link TrackDescriptor} */
|
|
165
|
+
clip(descriptor: TrackDescriptor, options?: AnimationClipOptions): AnimationTrackBuilder;
|
|
166
|
+
/** Adds a clip from multiple {@link TrackDescriptor}s */
|
|
167
|
+
clip(descriptors: TrackDescriptor[], options?: AnimationClipOptions): AnimationTrackBuilder;
|
|
168
|
+
/** Adds an animation track for an Object3D's position or scale */
|
|
169
|
+
track(target: Object3D, property: "position" | "scale", keyframes: KF<Vec3Value>, options?: TrackOptions): AnimationTrackBuilder;
|
|
170
|
+
/** Adds an animation track for an Object3D's quaternion */
|
|
171
|
+
track(target: Object3D, property: "quaternion", keyframes: KF<QuatValue>, options?: TrackOptions): AnimationTrackBuilder;
|
|
172
|
+
/** Adds an animation track for an Object3D's rotation (Euler, converted to quaternion) */
|
|
173
|
+
track(target: Object3D, property: "rotation", keyframes: KF<EulerValue>, options?: TrackOptions): AnimationTrackBuilder;
|
|
174
|
+
/** Adds an animation track for an Object3D's visibility */
|
|
175
|
+
track(target: Object3D, property: "visible", keyframes: KF<boolean>, options?: TrackOptions): AnimationTrackBuilder;
|
|
176
|
+
/** Adds an animation track for a material's numeric property */
|
|
177
|
+
track(target: Material, property: "opacity" | "roughness" | "metalness" | "alphaTest" | "emissiveIntensity" | "envMapIntensity" | "bumpScale" | "displacementScale" | "displacementBias", keyframes: KF<number>, options?: TrackOptions): AnimationTrackBuilder;
|
|
178
|
+
/** Adds an animation track for a material's color property */
|
|
179
|
+
track(target: Material, property: "color" | "emissive", keyframes: KF<ColorValue>, options?: TrackOptions): AnimationTrackBuilder;
|
|
180
|
+
/** Adds an animation track for a light's numeric property */
|
|
181
|
+
track(target: Light, property: "intensity" | "distance" | "angle" | "penumbra" | "decay", keyframes: KF<number>, options?: TrackOptions): AnimationTrackBuilder;
|
|
182
|
+
/** Adds an animation track for a light's color */
|
|
183
|
+
track(target: Light, property: "color", keyframes: KF<ColorValue>, options?: TrackOptions): AnimationTrackBuilder;
|
|
184
|
+
/** Adds an animation track for a camera's numeric property */
|
|
185
|
+
track(target: PerspectiveCamera, property: "fov" | "near" | "far" | "zoom", keyframes: KF<number>, options?: TrackOptions): AnimationTrackBuilder;
|
|
186
|
+
/** Mutes this track so it is skipped during playback */
|
|
187
|
+
muted(muted?: boolean): AnimationTrackBuilder;
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
/**
|
|
191
|
+
* Builder for audio tracks. Provides `.clip()` for adding audio clips by URL.
|
|
192
|
+
* @category Animation and Sequencing
|
|
193
|
+
*/
|
|
194
|
+
export interface AudioTrackBuilder extends TimelineBuilderBase {
|
|
195
|
+
/** Adds an audio clip by URL */
|
|
196
|
+
clip(url: string, options: AudioClipOptions): AudioTrackBuilder;
|
|
197
|
+
/** Mutes this track so it is skipped during playback */
|
|
198
|
+
muted(muted?: boolean): AudioTrackBuilder;
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
/**
|
|
202
|
+
* Builder for activation tracks. Provides `.clip()` for defining activation windows.
|
|
203
|
+
* @category Animation and Sequencing
|
|
204
|
+
*/
|
|
205
|
+
export interface ActivationTrackBuilder extends TimelineBuilderBase {
|
|
206
|
+
/** Adds an activation clip that shows/hides the bound object */
|
|
207
|
+
clip(options: ActivationClipOptions): ActivationTrackBuilder;
|
|
208
|
+
/** Mutes this track so it is skipped during playback */
|
|
209
|
+
muted(muted?: boolean): ActivationTrackBuilder;
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
/**
|
|
213
|
+
* Builder for control tracks. Provides `.clip()` for controlling nested objects/timelines.
|
|
214
|
+
* @category Animation and Sequencing
|
|
215
|
+
*/
|
|
216
|
+
export interface ControlTrackBuilder extends TimelineBuilderBase {
|
|
217
|
+
/** Adds a control clip for a source object */
|
|
218
|
+
clip(sourceObject: Object3D, options: ControlClipOptions): ControlTrackBuilder;
|
|
219
|
+
/** Mutes this track so it is skipped during playback */
|
|
220
|
+
muted(muted?: boolean): ControlTrackBuilder;
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
/**
|
|
224
|
+
* Builder for signal tracks. Provides `.signal()` for callback-based signals and `.marker()` for asset-based markers.
|
|
225
|
+
* @category Animation and Sequencing
|
|
226
|
+
*/
|
|
227
|
+
export interface SignalTrackBuilder extends TimelineBuilderBase {
|
|
228
|
+
/** Adds a signal with a callback that fires at the given time */
|
|
229
|
+
signal(time: number, callback: Function, options?: SignalMarkerOptions): SignalTrackBuilder;
|
|
230
|
+
/** Adds a signal marker referencing a signal asset by guid */
|
|
231
|
+
marker(time: number, asset: string, options?: SignalMarkerOptions): SignalTrackBuilder;
|
|
232
|
+
/** Mutes this track so it is skipped during playback */
|
|
233
|
+
muted(muted?: boolean): SignalTrackBuilder;
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
/**
|
|
237
|
+
* Builder for marker tracks. Provides `.marker()` for adding markers.
|
|
238
|
+
* @category Animation and Sequencing
|
|
239
|
+
*/
|
|
240
|
+
export interface MarkerTrackBuilder extends TimelineBuilderBase {
|
|
241
|
+
/** Adds a marker referencing a signal asset by guid */
|
|
242
|
+
marker(time: number, asset: string, options?: SignalMarkerOptions): MarkerTrackBuilder;
|
|
243
|
+
/** Mutes this track so it is skipped during playback */
|
|
244
|
+
muted(muted?: boolean): MarkerTrackBuilder;
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
|
|
248
|
+
/**
|
|
249
|
+
* A fluent builder for creating timeline assets ({@link TimelineAssetModel}) from code.
|
|
250
|
+
*
|
|
251
|
+
* Use {@link TimelineBuilder.create} to start building a timeline.
|
|
252
|
+
*
|
|
253
|
+
* @example Using build() for timelines without signal callbacks
|
|
254
|
+
* ```ts
|
|
255
|
+
* const timeline = TimelineBuilder.create("MySequence")
|
|
256
|
+
* .animationTrack("Character", animator)
|
|
257
|
+
* .clip(walkClip, { duration: 2, easeIn: 0.3 })
|
|
258
|
+
* .clip(runClip, { duration: 3, easeIn: 0.5, easeOut: 0.5 })
|
|
259
|
+
* .activationTrack("FX", particleObject)
|
|
260
|
+
* .clip({ start: 1, duration: 2 })
|
|
261
|
+
* .audioTrack("Music", audioSource)
|
|
262
|
+
* .clip("music.mp3", { start: 0, duration: 5, volume: 0.8 })
|
|
263
|
+
* .build();
|
|
264
|
+
*
|
|
265
|
+
* director.playableAsset = timeline;
|
|
266
|
+
* director.play();
|
|
267
|
+
* ```
|
|
268
|
+
*
|
|
269
|
+
* @example With inline tracks (no pre-built clips needed)
|
|
270
|
+
* ```ts
|
|
271
|
+
* TimelineBuilder.create("DoorSequence")
|
|
272
|
+
* .animationTrack("Door", door)
|
|
273
|
+
* .track(door, "position", { from: [0, 0, 0], to: [2, 0, 0], duration: 1 })
|
|
274
|
+
* .track(light, "intensity", { from: 0, to: 5, duration: 1 })
|
|
275
|
+
* .signalTrack("Events")
|
|
276
|
+
* .signal(0.5, () => playSound("creak"))
|
|
277
|
+
* .install(director);
|
|
278
|
+
*
|
|
279
|
+
* director.play();
|
|
280
|
+
* ```
|
|
281
|
+
*
|
|
282
|
+
* @example Using install() with signal callbacks
|
|
283
|
+
* ```ts
|
|
284
|
+
* TimelineBuilder.create("WithSignals")
|
|
285
|
+
* .animationTrack("Character", animator)
|
|
286
|
+
* .clip(walkClip, { duration: 2 })
|
|
287
|
+
* .signalTrack("Events")
|
|
288
|
+
* .signal(1.0, () => console.log("1 second!"))
|
|
289
|
+
* .signal(2.0, () => spawnParticles())
|
|
290
|
+
* .install(director);
|
|
291
|
+
*
|
|
292
|
+
* director.play();
|
|
293
|
+
* ```
|
|
294
|
+
*
|
|
295
|
+
* @category Animation and Sequencing
|
|
296
|
+
* @group Utilities
|
|
297
|
+
*/
|
|
298
|
+
export class TimelineBuilder {
|
|
299
|
+
private _name: string;
|
|
300
|
+
private _tracks: BuilderTrack[] = [];
|
|
301
|
+
private _currentTrack: BuilderTrack | null = null;
|
|
302
|
+
private _pendingSignals: PendingSignal[] = [];
|
|
303
|
+
private _idProvider: InstantiateIdProvider;
|
|
304
|
+
|
|
305
|
+
private constructor(name: string, seed?: number) {
|
|
306
|
+
this._name = name;
|
|
307
|
+
this._idProvider = new InstantiateIdProvider(seed ?? Date.now());
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
/**
|
|
311
|
+
* Creates a new TimelineBuilder instance.
|
|
312
|
+
* @param name - Name for the timeline asset
|
|
313
|
+
* @param seed - Optional numeric seed for deterministic guid generation. Defaults to `Date.now()`.
|
|
314
|
+
*/
|
|
315
|
+
static create(name?: string, seed?: number): TimelineBuilderBase {
|
|
316
|
+
return new TimelineBuilder(name ?? "Timeline", seed);
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
// #region Track creation
|
|
320
|
+
|
|
321
|
+
/**
|
|
322
|
+
* Adds an animation track. Chain `.clip()` calls to add pre-built clips,
|
|
323
|
+
* or chain `.track()` calls to define animation data inline:
|
|
324
|
+
*
|
|
325
|
+
* @example With pre-built AnimationClip
|
|
326
|
+
* ```ts
|
|
327
|
+
* .animationTrack("Character", animator)
|
|
328
|
+
* .clip(walkClip, { duration: 2, easeIn: 0.3 })
|
|
329
|
+
* .clip(runClip, { duration: 3 })
|
|
330
|
+
* ```
|
|
331
|
+
*
|
|
332
|
+
* @example With inline tracks
|
|
333
|
+
* ```ts
|
|
334
|
+
* .animationTrack("Door", door)
|
|
335
|
+
* .track(door, "position", { from: [0, 0, 0], to: [2, 0, 0], duration: 1 })
|
|
336
|
+
* .track(light, "intensity", { from: 0, to: 5, duration: 1 })
|
|
337
|
+
* ```
|
|
338
|
+
*
|
|
339
|
+
* @param name - Display name for the track
|
|
340
|
+
* @param binding - The Animator or Object3D to animate
|
|
341
|
+
*/
|
|
342
|
+
animationTrack(name: string, binding?: Animator | Object3D | null): AnimationTrackBuilder {
|
|
343
|
+
this.commitInlineTracks();
|
|
344
|
+
this._currentTrack = this.pushTrack(name, TrackType.Animation, binding ?? null);
|
|
345
|
+
return this as unknown as AnimationTrackBuilder;
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
/**
|
|
349
|
+
* Adds an audio track. Subsequent `.clip()` calls add audio clips to this track.
|
|
350
|
+
* @param name - Display name for the track
|
|
351
|
+
* @param binding - The AudioSource to play audio on (optional)
|
|
352
|
+
* @param volume - Track volume multiplier (default: 1)
|
|
353
|
+
*/
|
|
354
|
+
audioTrack(name: string, binding?: AudioSource | Object3D | null, volume?: number): AudioTrackBuilder {
|
|
355
|
+
this.commitInlineTracks();
|
|
356
|
+
this._currentTrack = this.pushTrack(name, TrackType.Audio, binding ?? null);
|
|
357
|
+
this._currentTrack.volume = volume;
|
|
358
|
+
return this as unknown as AudioTrackBuilder;
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
/**
|
|
362
|
+
* Adds an activation track. Subsequent `.clip()` calls define when the bound object is active.
|
|
363
|
+
* @param name - Display name for the track
|
|
364
|
+
* @param binding - The Object3D to show/hide
|
|
365
|
+
*/
|
|
366
|
+
activationTrack(name: string, binding?: Object3D | null): ActivationTrackBuilder {
|
|
367
|
+
this.commitInlineTracks();
|
|
368
|
+
this._currentTrack = this.pushTrack(name, TrackType.Activation, binding ?? null);
|
|
369
|
+
return this as unknown as ActivationTrackBuilder;
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
/**
|
|
373
|
+
* Adds a control track. Subsequent `.clip()` calls control nested timelines or objects.
|
|
374
|
+
* @param name - Display name for the track
|
|
375
|
+
*/
|
|
376
|
+
controlTrack(name: string): ControlTrackBuilder {
|
|
377
|
+
this.commitInlineTracks();
|
|
378
|
+
this._currentTrack = this.pushTrack(name, TrackType.Control, null);
|
|
379
|
+
return this as unknown as ControlTrackBuilder;
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
/**
|
|
383
|
+
* Adds a signal track. Use `.signal()` or `.marker()` to add signal markers.
|
|
384
|
+
* @param name - Display name for the track
|
|
385
|
+
* @param binding - The SignalReceiver component (optional — if using `.signal()` with callbacks, one is created automatically by {@link install})
|
|
386
|
+
*/
|
|
387
|
+
signalTrack(name: string, binding?: SignalReceiver | Object3D | null): SignalTrackBuilder {
|
|
388
|
+
this.commitInlineTracks();
|
|
389
|
+
this._currentTrack = this.pushTrack(name, TrackType.Signal, binding ?? null);
|
|
390
|
+
return this as unknown as SignalTrackBuilder;
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
/**
|
|
394
|
+
* Adds a marker track. Use `.marker()` to add markers.
|
|
395
|
+
* @param name - Display name for the track
|
|
396
|
+
*/
|
|
397
|
+
markerTrack(name: string): MarkerTrackBuilder {
|
|
398
|
+
this.commitInlineTracks();
|
|
399
|
+
this._currentTrack = this.pushTrack(name, TrackType.Marker, null);
|
|
400
|
+
return this as unknown as MarkerTrackBuilder;
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
// #endregion
|
|
404
|
+
|
|
405
|
+
// #region Clip and marker methods
|
|
406
|
+
|
|
407
|
+
/**
|
|
408
|
+
* Adds a clip to the current track. The clip type must match the track type.
|
|
409
|
+
*
|
|
410
|
+
* - On an **animation track**: pass an `AnimationClip`, a {@link TrackDescriptor}, or a `TrackDescriptor[]` — and optional {@link AnimationClipOptions}
|
|
411
|
+
* - On an **audio track**: pass a clip URL (string) and {@link AudioClipOptions}
|
|
412
|
+
* - On an **activation track**: pass {@link ActivationClipOptions}
|
|
413
|
+
* - On a **control track**: pass an Object3D and {@link ControlClipOptions}
|
|
414
|
+
*/
|
|
415
|
+
clip(asset: AnimationClip, options?: AnimationClipOptions): this;
|
|
416
|
+
clip(descriptor: TrackDescriptor, options?: AnimationClipOptions): this;
|
|
417
|
+
clip(descriptors: TrackDescriptor[], options?: AnimationClipOptions): this;
|
|
418
|
+
clip(url: string, options: AudioClipOptions): this;
|
|
419
|
+
clip(options: ActivationClipOptions): this;
|
|
420
|
+
clip(sourceObject: Object3D, options: ControlClipOptions): this;
|
|
421
|
+
clip(assetOrOptions: AnimationClip | TrackDescriptor | TrackDescriptor[] | string | Object3D | ActivationClipOptions, options?: AnimationClipOptions | AudioClipOptions | ControlClipOptions): this {
|
|
422
|
+
if (!this._currentTrack) throw new Error("TimelineBuilder: .clip() must be called after a track method (e.g. .animationTrack())");
|
|
423
|
+
this.commitInlineTracks();
|
|
424
|
+
|
|
425
|
+
const track = this._currentTrack;
|
|
426
|
+
|
|
427
|
+
switch (track.type) {
|
|
428
|
+
case TrackType.Animation: {
|
|
429
|
+
// Resolve TrackDescriptor(s) to AnimationClip if needed
|
|
430
|
+
let animClip: AnimationClip;
|
|
431
|
+
if (assetOrOptions instanceof AnimationClip) {
|
|
432
|
+
animClip = assetOrOptions;
|
|
433
|
+
}
|
|
434
|
+
else {
|
|
435
|
+
const descriptors = Array.isArray(assetOrOptions) ? assetOrOptions : [assetOrOptions as TrackDescriptor];
|
|
436
|
+
// Use the track's binding as root for resolution
|
|
437
|
+
const binding = track.outputs[0];
|
|
438
|
+
const root = binding instanceof Object3D ? binding
|
|
439
|
+
: (binding != null && "gameObject" in binding) ? (binding as any).gameObject as Object3D
|
|
440
|
+
: undefined;
|
|
441
|
+
animClip = resolveToClip(descriptors, root);
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
const opts = (options ?? {}) as AnimationClipOptions;
|
|
445
|
+
const duration = opts.duration ?? animClip.duration;
|
|
446
|
+
const start = opts.start ?? track.cursor;
|
|
447
|
+
const end = start + duration;
|
|
448
|
+
|
|
449
|
+
const clipModel: ClipModel = {
|
|
450
|
+
start,
|
|
451
|
+
end,
|
|
452
|
+
duration,
|
|
453
|
+
timeScale: opts.speed ?? 1,
|
|
454
|
+
clipIn: opts.clipIn ?? 0,
|
|
455
|
+
easeInDuration: opts.easeIn ?? 0,
|
|
456
|
+
easeOutDuration: opts.easeOut ?? 0,
|
|
457
|
+
preExtrapolationMode: opts.preExtrapolation ?? ClipExtrapolation.None,
|
|
458
|
+
postExtrapolationMode: opts.postExtrapolation ?? ClipExtrapolation.None,
|
|
459
|
+
reversed: opts.reversed,
|
|
460
|
+
asset: {
|
|
461
|
+
clip: animClip,
|
|
462
|
+
loop: opts.loop ?? false,
|
|
463
|
+
duration: animClip.duration,
|
|
464
|
+
removeStartOffset: opts.removeStartOffset ?? false,
|
|
465
|
+
} satisfies AnimationClipModel,
|
|
466
|
+
};
|
|
467
|
+
track.clips.push(clipModel);
|
|
468
|
+
track.cursor = end;
|
|
469
|
+
break;
|
|
470
|
+
}
|
|
471
|
+
|
|
472
|
+
case TrackType.Audio: {
|
|
473
|
+
const url = assetOrOptions as string;
|
|
474
|
+
const opts = (options ?? {}) as AudioClipOptions;
|
|
475
|
+
const duration = opts.duration;
|
|
476
|
+
const start = opts.start ?? track.cursor;
|
|
477
|
+
const end = start + duration;
|
|
478
|
+
|
|
479
|
+
const clipModel: ClipModel = {
|
|
480
|
+
start,
|
|
481
|
+
end,
|
|
482
|
+
duration,
|
|
483
|
+
timeScale: opts.speed ?? 1,
|
|
484
|
+
clipIn: 0,
|
|
485
|
+
easeInDuration: opts.easeIn ?? 0,
|
|
486
|
+
easeOutDuration: opts.easeOut ?? 0,
|
|
487
|
+
preExtrapolationMode: ClipExtrapolation.None,
|
|
488
|
+
postExtrapolationMode: ClipExtrapolation.None,
|
|
489
|
+
asset: {
|
|
490
|
+
clip: url,
|
|
491
|
+
loop: opts.loop ?? false,
|
|
492
|
+
volume: opts.volume ?? 1,
|
|
493
|
+
} satisfies AudioClipModel,
|
|
494
|
+
};
|
|
495
|
+
track.clips.push(clipModel);
|
|
496
|
+
track.cursor = end;
|
|
497
|
+
break;
|
|
498
|
+
}
|
|
499
|
+
|
|
500
|
+
case TrackType.Activation: {
|
|
501
|
+
const opts = assetOrOptions as ActivationClipOptions;
|
|
502
|
+
const start = opts.start ?? track.cursor;
|
|
503
|
+
const end = start + opts.duration;
|
|
504
|
+
|
|
505
|
+
const clipModel: ClipModel = {
|
|
506
|
+
start,
|
|
507
|
+
end,
|
|
508
|
+
duration: opts.duration,
|
|
509
|
+
timeScale: 1,
|
|
510
|
+
clipIn: 0,
|
|
511
|
+
easeInDuration: opts.easeIn ?? 0,
|
|
512
|
+
easeOutDuration: opts.easeOut ?? 0,
|
|
513
|
+
preExtrapolationMode: ClipExtrapolation.None,
|
|
514
|
+
postExtrapolationMode: ClipExtrapolation.None,
|
|
515
|
+
asset: {},
|
|
516
|
+
};
|
|
517
|
+
track.clips.push(clipModel);
|
|
518
|
+
track.cursor = end;
|
|
519
|
+
break;
|
|
520
|
+
}
|
|
521
|
+
|
|
522
|
+
case TrackType.Control: {
|
|
523
|
+
const sourceObject = assetOrOptions as Object3D;
|
|
524
|
+
const opts = (options ?? {}) as ControlClipOptions;
|
|
525
|
+
const start = opts.start ?? track.cursor;
|
|
526
|
+
const duration = opts.duration;
|
|
527
|
+
const end = start + duration;
|
|
528
|
+
|
|
529
|
+
const clipModel: ClipModel = {
|
|
530
|
+
start,
|
|
531
|
+
end,
|
|
532
|
+
duration,
|
|
533
|
+
timeScale: 1,
|
|
534
|
+
clipIn: 0,
|
|
535
|
+
easeInDuration: 0,
|
|
536
|
+
easeOutDuration: 0,
|
|
537
|
+
preExtrapolationMode: ClipExtrapolation.None,
|
|
538
|
+
postExtrapolationMode: ClipExtrapolation.None,
|
|
539
|
+
asset: {
|
|
540
|
+
sourceObject,
|
|
541
|
+
controlActivation: opts.controlActivation ?? true,
|
|
542
|
+
updateDirector: opts.updateDirector ?? true,
|
|
543
|
+
} satisfies ControlClipModel,
|
|
544
|
+
};
|
|
545
|
+
track.clips.push(clipModel);
|
|
546
|
+
track.cursor = end;
|
|
547
|
+
break;
|
|
548
|
+
}
|
|
549
|
+
|
|
550
|
+
default:
|
|
551
|
+
throw new Error(`TimelineBuilder: .clip() is not supported on track type "${track.type}"`);
|
|
552
|
+
}
|
|
553
|
+
|
|
554
|
+
return this;
|
|
555
|
+
}
|
|
556
|
+
|
|
557
|
+
/**
|
|
558
|
+
* Adds a signal marker to the current signal or marker track.
|
|
559
|
+
* @param time - Time in seconds when the signal fires
|
|
560
|
+
* @param asset - The signal asset identifier (guid string)
|
|
561
|
+
* @param options - Optional marker configuration
|
|
562
|
+
*/
|
|
563
|
+
marker(time: number, asset: string, options?: SignalMarkerOptions): this {
|
|
564
|
+
if (!this._currentTrack) throw new Error("TimelineBuilder: .marker() must be called after a track method");
|
|
565
|
+
if (this._currentTrack.type !== TrackType.Signal && this._currentTrack.type !== TrackType.Marker) {
|
|
566
|
+
throw new Error(`TimelineBuilder: .marker() is only supported on signal and marker tracks, not "${this._currentTrack.type}"`);
|
|
567
|
+
}
|
|
568
|
+
|
|
569
|
+
const marker: SignalMarkerModel = {
|
|
570
|
+
type: MarkerType.Signal,
|
|
571
|
+
time,
|
|
572
|
+
retroActive: options?.retroActive ?? false,
|
|
573
|
+
emitOnce: options?.emitOnce ?? false,
|
|
574
|
+
asset,
|
|
575
|
+
};
|
|
576
|
+
this._currentTrack.markers.push(marker);
|
|
577
|
+
|
|
578
|
+
// Update cursor past the marker
|
|
579
|
+
if (time > this._currentTrack.cursor) {
|
|
580
|
+
this._currentTrack.cursor = time;
|
|
581
|
+
}
|
|
582
|
+
|
|
583
|
+
return this;
|
|
584
|
+
}
|
|
585
|
+
|
|
586
|
+
/**
|
|
587
|
+
* Adds a signal with a callback to the current signal track.
|
|
588
|
+
* This is a convenience method that automatically generates a signal asset guid,
|
|
589
|
+
* adds the marker, and registers the callback so that {@link install} can wire up
|
|
590
|
+
* the `SignalReceiver` on the director's GameObject.
|
|
591
|
+
*
|
|
592
|
+
* @param time - Time in seconds when the signal fires
|
|
593
|
+
* @param callback - The function to invoke when the signal fires
|
|
594
|
+
* @param options - Optional marker configuration
|
|
595
|
+
*
|
|
596
|
+
* @example
|
|
597
|
+
* ```ts
|
|
598
|
+
* const timeline = TimelineBuilder.create("Sequence")
|
|
599
|
+
* .signalTrack("Events")
|
|
600
|
+
* .signal(1.0, () => console.log("1 second reached!"))
|
|
601
|
+
* .signal(3.5, () => console.log("halfway!"), { emitOnce: true })
|
|
602
|
+
* .install(director);
|
|
603
|
+
* ```
|
|
604
|
+
*/
|
|
605
|
+
signal(time: number, callback: Function, options?: SignalMarkerOptions): this {
|
|
606
|
+
if (!this._currentTrack) throw new Error("TimelineBuilder: .signal() must be called after a track method");
|
|
607
|
+
if (this._currentTrack.type !== TrackType.Signal && this._currentTrack.type !== TrackType.Marker) {
|
|
608
|
+
throw new Error(`TimelineBuilder: .signal() is only supported on signal and marker tracks, not "${this._currentTrack.type}"`);
|
|
609
|
+
}
|
|
610
|
+
|
|
611
|
+
const guid = this._idProvider.generateUUID();
|
|
612
|
+
const trackIndex = this._tracks.indexOf(this._currentTrack);
|
|
613
|
+
|
|
614
|
+
// Add the marker with the generated guid
|
|
615
|
+
const marker: SignalMarkerModel = {
|
|
616
|
+
type: MarkerType.Signal,
|
|
617
|
+
time,
|
|
618
|
+
retroActive: options?.retroActive ?? false,
|
|
619
|
+
emitOnce: options?.emitOnce ?? false,
|
|
620
|
+
asset: guid,
|
|
621
|
+
};
|
|
622
|
+
this._currentTrack.markers.push(marker);
|
|
623
|
+
|
|
624
|
+
// Store the pending signal for wiring during install()
|
|
625
|
+
this._pendingSignals.push({ trackIndex, guid, callback });
|
|
626
|
+
|
|
627
|
+
if (time > this._currentTrack.cursor) {
|
|
628
|
+
this._currentTrack.cursor = time;
|
|
629
|
+
}
|
|
630
|
+
|
|
631
|
+
return this;
|
|
632
|
+
}
|
|
633
|
+
|
|
634
|
+
/**
|
|
635
|
+
* Mutes the current track so it is skipped during playback.
|
|
636
|
+
*/
|
|
637
|
+
muted(muted: boolean = true): this {
|
|
638
|
+
if (!this._currentTrack) throw new Error("TimelineBuilder: .muted() must be called after a track method");
|
|
639
|
+
this._currentTrack.muted = muted;
|
|
640
|
+
return this;
|
|
641
|
+
}
|
|
642
|
+
|
|
643
|
+
// --- Object3D ---
|
|
644
|
+
/** Adds an animation track descriptor for an Object3D's position or scale to the current animation track */
|
|
645
|
+
track(target: Object3D, property: "position" | "scale", keyframes: KF<Vec3Value>, options?: TrackOptions): this;
|
|
646
|
+
/** Adds an animation track descriptor for an Object3D's quaternion to the current animation track */
|
|
647
|
+
track(target: Object3D, property: "quaternion", keyframes: KF<QuatValue>, options?: TrackOptions): this;
|
|
648
|
+
/** Adds an animation track descriptor for an Object3D's rotation (Euler, converted to quaternion) to the current animation track */
|
|
649
|
+
track(target: Object3D, property: "rotation", keyframes: KF<EulerValue>, options?: TrackOptions): this;
|
|
650
|
+
/** Adds an animation track descriptor for an Object3D's visibility to the current animation track */
|
|
651
|
+
track(target: Object3D, property: "visible", keyframes: KF<boolean>, options?: TrackOptions): this;
|
|
652
|
+
// --- Material ---
|
|
653
|
+
/** Adds an animation track descriptor for a material's numeric property to the current animation track */
|
|
654
|
+
track(target: Material, property: "opacity" | "roughness" | "metalness" | "alphaTest" | "emissiveIntensity" | "envMapIntensity" | "bumpScale" | "displacementScale" | "displacementBias", keyframes: KF<number>, options?: TrackOptions): this;
|
|
655
|
+
/** Adds an animation track descriptor for a material's color property to the current animation track */
|
|
656
|
+
track(target: Material, property: "color" | "emissive", keyframes: KF<ColorValue>, options?: TrackOptions): this;
|
|
657
|
+
// --- Light ---
|
|
658
|
+
/** Adds an animation track descriptor for a light's numeric property to the current animation track */
|
|
659
|
+
track(target: Light, property: "intensity" | "distance" | "angle" | "penumbra" | "decay", keyframes: KF<number>, options?: TrackOptions): this;
|
|
660
|
+
/** Adds an animation track descriptor for a light's color to the current animation track */
|
|
661
|
+
track(target: Light, property: "color", keyframes: KF<ColorValue>, options?: TrackOptions): this;
|
|
662
|
+
// --- Camera ---
|
|
663
|
+
/** Adds an animation track descriptor for a camera's numeric property to the current animation track */
|
|
664
|
+
track(target: PerspectiveCamera, property: "fov" | "near" | "far" | "zoom", keyframes: KF<number>, options?: TrackOptions): this;
|
|
665
|
+
/**
|
|
666
|
+
* Adds an animation track descriptor to the current animation track.
|
|
667
|
+
* Multiple `.track()` calls accumulate into a single animation clip that is
|
|
668
|
+
* committed when the next `.clip()`, track method, or `.build()`/`.install()` is called.
|
|
669
|
+
*
|
|
670
|
+
* Must be called after `.animationTrack()`.
|
|
671
|
+
*
|
|
672
|
+
* @param target - The object whose type determines valid properties and value types
|
|
673
|
+
* @param property - The property to animate
|
|
674
|
+
* @param keyframes - Keyframe array or {@link Tween} shorthand
|
|
675
|
+
* @param options - Optional {@link TrackOptions} with a `root` for named targeting
|
|
676
|
+
*/
|
|
677
|
+
track(target: object, property: string, keyframes: KF<any>, options?: TrackOptions): this {
|
|
678
|
+
if (!this._currentTrack) throw new Error("TimelineBuilder: .track() must be called after .animationTrack()");
|
|
679
|
+
if (this._currentTrack.type !== TrackType.Animation) throw new Error("TimelineBuilder: .track() is only supported on animation tracks");
|
|
680
|
+
this._currentTrack.inlineTracks.push(trackFn(target as Object3D, property as "position", keyframes, options));
|
|
681
|
+
return this;
|
|
682
|
+
}
|
|
683
|
+
|
|
684
|
+
// #endregion
|
|
685
|
+
|
|
686
|
+
/**
|
|
687
|
+
* Builds and returns the {@link TimelineAssetModel}.
|
|
688
|
+
* Assign the result to `PlayableDirector.playableAsset` to play it.
|
|
689
|
+
*
|
|
690
|
+
* If you used `.signal()` with callbacks, use {@link install} instead — it calls `build()`
|
|
691
|
+
* internally and also wires up the SignalReceiver on the director's GameObject.
|
|
692
|
+
*/
|
|
693
|
+
build(): TimelineAssetModel {
|
|
694
|
+
this.commitInlineTracks();
|
|
695
|
+
const tracks: TrackModel[] = this._tracks.map(t => {
|
|
696
|
+
const track: TrackModel = {
|
|
697
|
+
name: t.name,
|
|
698
|
+
type: t.type,
|
|
699
|
+
muted: t.muted,
|
|
700
|
+
outputs: t.outputs,
|
|
701
|
+
};
|
|
702
|
+
if (t.clips.length > 0) track.clips = t.clips;
|
|
703
|
+
if (t.markers.length > 0) track.markers = t.markers;
|
|
704
|
+
if (t.volume !== undefined) track.volume = t.volume;
|
|
705
|
+
if (t.trackOffset !== undefined) track.trackOffset = t.trackOffset;
|
|
706
|
+
return track;
|
|
707
|
+
});
|
|
708
|
+
|
|
709
|
+
return {
|
|
710
|
+
name: this._name,
|
|
711
|
+
tracks,
|
|
712
|
+
};
|
|
713
|
+
}
|
|
714
|
+
|
|
715
|
+
/**
|
|
716
|
+
* Builds the timeline asset, assigns it to the director, and wires up any
|
|
717
|
+
* `.signal()` callbacks by creating/configuring a {@link SignalReceiver} on the
|
|
718
|
+
* director's GameObject.
|
|
719
|
+
*
|
|
720
|
+
* @param director - The PlayableDirector to install the timeline on
|
|
721
|
+
* @returns The built TimelineAssetModel (also assigned to `director.playableAsset`)
|
|
722
|
+
*
|
|
723
|
+
* @example
|
|
724
|
+
* ```ts
|
|
725
|
+
* TimelineBuilder.create("MyTimeline")
|
|
726
|
+
* .animationTrack("Anim", animator)
|
|
727
|
+
* .clip(walkClip, { duration: 2 })
|
|
728
|
+
* .signalTrack("Events")
|
|
729
|
+
* .signal(1.0, () => console.log("signal fired!"))
|
|
730
|
+
* .install(director);
|
|
731
|
+
*
|
|
732
|
+
* director.play();
|
|
733
|
+
* ```
|
|
734
|
+
*/
|
|
735
|
+
install(director: PlayableDirector): TimelineAssetModel {
|
|
736
|
+
const asset = this.build();
|
|
737
|
+
|
|
738
|
+
// Wire up signal callbacks
|
|
739
|
+
if (this._pendingSignals.length > 0) {
|
|
740
|
+
const obj = director.gameObject;
|
|
741
|
+
let receiver = GameObject.getComponent(obj, SignalReceiver);
|
|
742
|
+
if (!receiver) {
|
|
743
|
+
receiver = GameObject.addComponent(obj, SignalReceiver);
|
|
744
|
+
}
|
|
745
|
+
if (!receiver.events) {
|
|
746
|
+
receiver.events = [];
|
|
747
|
+
}
|
|
748
|
+
|
|
749
|
+
for (const pending of this._pendingSignals) {
|
|
750
|
+
const signalAsset = new SignalAsset();
|
|
751
|
+
signalAsset.guid = pending.guid;
|
|
752
|
+
|
|
753
|
+
const evt = new SignalReceiverEvent();
|
|
754
|
+
evt.signal = signalAsset;
|
|
755
|
+
evt.reaction = new EventList([pending.callback]);
|
|
756
|
+
receiver.events.push(evt);
|
|
757
|
+
|
|
758
|
+
// Wire the receiver as the output binding for the signal track
|
|
759
|
+
const track = asset.tracks[pending.trackIndex];
|
|
760
|
+
if (track && !track.outputs.includes(receiver)) {
|
|
761
|
+
track.outputs.push(receiver);
|
|
762
|
+
}
|
|
763
|
+
}
|
|
764
|
+
}
|
|
765
|
+
|
|
766
|
+
director.playableAsset = asset;
|
|
767
|
+
return asset;
|
|
768
|
+
}
|
|
769
|
+
|
|
770
|
+
// #region Private helpers
|
|
771
|
+
|
|
772
|
+
private pushTrack(name: string, type: TrackType, binding: object | null): BuilderTrack {
|
|
773
|
+
const track: BuilderTrack = {
|
|
774
|
+
name,
|
|
775
|
+
type,
|
|
776
|
+
muted: false,
|
|
777
|
+
outputs: binding ? [binding] : [],
|
|
778
|
+
clips: [],
|
|
779
|
+
markers: [],
|
|
780
|
+
cursor: 0,
|
|
781
|
+
inlineTracks: [],
|
|
782
|
+
};
|
|
783
|
+
this._tracks.push(track);
|
|
784
|
+
return track;
|
|
785
|
+
}
|
|
786
|
+
|
|
787
|
+
/** Commits any pending `.track()` descriptors on the current animation track into a clip */
|
|
788
|
+
private commitInlineTracks(): void {
|
|
789
|
+
if (!this._currentTrack || this._currentTrack.inlineTracks.length === 0) return;
|
|
790
|
+
|
|
791
|
+
const t = this._currentTrack;
|
|
792
|
+
const binding = t.outputs[0];
|
|
793
|
+
const root = binding instanceof Object3D ? binding
|
|
794
|
+
: (binding != null && "gameObject" in binding) ? (binding as any).gameObject as Object3D
|
|
795
|
+
: undefined;
|
|
796
|
+
const animClip = resolveToClip(t.inlineTracks, root);
|
|
797
|
+
|
|
798
|
+
const start = t.cursor;
|
|
799
|
+
const duration = animClip.duration;
|
|
800
|
+
|
|
801
|
+
const clipModel: ClipModel = {
|
|
802
|
+
start,
|
|
803
|
+
end: start + duration,
|
|
804
|
+
duration,
|
|
805
|
+
timeScale: 1,
|
|
806
|
+
clipIn: 0,
|
|
807
|
+
easeInDuration: 0,
|
|
808
|
+
easeOutDuration: 0,
|
|
809
|
+
preExtrapolationMode: ClipExtrapolation.None,
|
|
810
|
+
postExtrapolationMode: ClipExtrapolation.None,
|
|
811
|
+
asset: {
|
|
812
|
+
clip: animClip,
|
|
813
|
+
loop: false,
|
|
814
|
+
duration: animClip.duration,
|
|
815
|
+
removeStartOffset: false,
|
|
816
|
+
} satisfies AnimationClipModel,
|
|
817
|
+
};
|
|
818
|
+
t.clips.push(clipModel);
|
|
819
|
+
t.cursor = start + duration;
|
|
820
|
+
t.inlineTracks = [];
|
|
821
|
+
}
|
|
822
|
+
|
|
823
|
+
// #endregion
|
|
824
|
+
}
|