@needle-tools/engine 5.1.0-alpha.4 → 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.
Files changed (96) hide show
  1. package/CHANGELOG.md +29 -0
  2. package/components.needle.json +1 -1
  3. package/dist/{needle-engine.bundle-B7cqsI4c.js → needle-engine.bundle-C-LG00ZZ.js} +6570 -6271
  4. package/dist/{needle-engine.bundle-AjVIot3d.min.js → needle-engine.bundle-D7tzaiYE.min.js} +157 -157
  5. package/dist/{needle-engine.bundle-DQCuBTVp.umd.cjs → needle-engine.bundle-OPkPmdUM.umd.cjs} +140 -140
  6. package/dist/needle-engine.d.ts +668 -191
  7. package/dist/needle-engine.js +597 -595
  8. package/dist/needle-engine.min.js +1 -1
  9. package/dist/needle-engine.umd.cjs +1 -1
  10. package/dist/three.js +1 -0
  11. package/dist/three.min.js +21 -21
  12. package/dist/three.umd.cjs +16 -16
  13. package/lib/engine/api.d.ts +2 -0
  14. package/lib/engine/api.js +2 -0
  15. package/lib/engine/api.js.map +1 -1
  16. package/lib/engine/codegen/register_types.js +10 -10
  17. package/lib/engine/codegen/register_types.js.map +1 -1
  18. package/lib/engine/engine_camera.fit.js +16 -4
  19. package/lib/engine/engine_camera.fit.js.map +1 -1
  20. package/lib/engine/engine_context.d.ts +20 -7
  21. package/lib/engine/engine_context.js +29 -14
  22. package/lib/engine/engine_context.js.map +1 -1
  23. package/lib/engine/engine_context_eventbus.d.ts +47 -0
  24. package/lib/engine/engine_context_eventbus.js +47 -0
  25. package/lib/engine/engine_context_eventbus.js.map +1 -0
  26. package/lib/engine/engine_input.d.ts +23 -4
  27. package/lib/engine/engine_input.js +2 -1
  28. package/lib/engine/engine_input.js.map +1 -1
  29. package/lib/engine/engine_physics_rapier.d.ts +10 -0
  30. package/lib/engine/engine_physics_rapier.js +6 -0
  31. package/lib/engine/engine_physics_rapier.js.map +1 -1
  32. package/lib/engine/engine_types.d.ts +10 -0
  33. package/lib/engine-components/AnimationBuilder.d.ts +158 -0
  34. package/lib/engine-components/AnimationBuilder.js +305 -0
  35. package/lib/engine-components/AnimationBuilder.js.map +1 -0
  36. package/lib/engine-components/Animator.js +6 -1
  37. package/lib/engine-components/Animator.js.map +1 -1
  38. package/lib/engine-components/AnimatorController.builder.d.ts +101 -23
  39. package/lib/engine-components/AnimatorController.builder.js +88 -20
  40. package/lib/engine-components/AnimatorController.builder.js.map +1 -1
  41. package/lib/engine-components/AnimatorController.js +2 -0
  42. package/lib/engine-components/AnimatorController.js.map +1 -1
  43. package/lib/engine-components/ContactShadows.d.ts +1 -0
  44. package/lib/engine-components/ContactShadows.js +14 -1
  45. package/lib/engine-components/ContactShadows.js.map +1 -1
  46. package/lib/engine-components/DropListener.js +3 -0
  47. package/lib/engine-components/DropListener.js.map +1 -1
  48. package/lib/engine-components/OrbitControls.d.ts +0 -2
  49. package/lib/engine-components/OrbitControls.js +14 -1
  50. package/lib/engine-components/OrbitControls.js.map +1 -1
  51. package/lib/engine-components/SceneSwitcher.js +3 -0
  52. package/lib/engine-components/SceneSwitcher.js.map +1 -1
  53. package/lib/engine-components/api.d.ts +1 -0
  54. package/lib/engine-components/api.js +1 -0
  55. package/lib/engine-components/api.js.map +1 -1
  56. package/lib/engine-components/codegen/components.d.ts +6 -6
  57. package/lib/engine-components/codegen/components.js +6 -6
  58. package/lib/engine-components/codegen/components.js.map +1 -1
  59. package/lib/engine-components/postprocessing/Effects/Tonemapping.utils.d.ts +1 -1
  60. package/lib/engine-components/timeline/PlayableDirector.d.ts +7 -7
  61. package/lib/engine-components/timeline/PlayableDirector.js +6 -6
  62. package/lib/engine-components/timeline/PlayableDirector.js.map +1 -1
  63. package/lib/engine-components/timeline/TimelineBuilder.d.ts +175 -9
  64. package/lib/engine-components/timeline/TimelineBuilder.js +108 -2
  65. package/lib/engine-components/timeline/TimelineBuilder.js.map +1 -1
  66. package/lib/engine-components/timeline/TimelineTracks.d.ts +15 -7
  67. package/lib/engine-components/timeline/TimelineTracks.js +22 -14
  68. package/lib/engine-components/timeline/TimelineTracks.js.map +1 -1
  69. package/lib/engine-components/web/CursorFollow.d.ts +0 -1
  70. package/lib/engine-components/web/CursorFollow.js +0 -1
  71. package/lib/engine-components/web/CursorFollow.js.map +1 -1
  72. package/package.json +1 -1
  73. package/plugins/common/cloud.js +6 -1
  74. package/plugins/vite/license.js +19 -1
  75. package/src/engine/api.ts +3 -0
  76. package/src/engine/codegen/register_types.ts +10 -10
  77. package/src/engine/engine_camera.fit.ts +15 -4
  78. package/src/engine/engine_context.ts +30 -15
  79. package/src/engine/engine_context_eventbus.ts +73 -0
  80. package/src/engine/engine_input.ts +27 -6
  81. package/src/engine/engine_physics_rapier.ts +20 -6
  82. package/src/engine/engine_types.ts +22 -12
  83. package/src/engine-components/AnimationBuilder.ts +472 -0
  84. package/src/engine-components/Animator.ts +6 -1
  85. package/src/engine-components/AnimatorController.builder.ts +163 -37
  86. package/src/engine-components/AnimatorController.ts +1 -0
  87. package/src/engine-components/ContactShadows.ts +15 -1
  88. package/src/engine-components/DropListener.ts +3 -0
  89. package/src/engine-components/OrbitControls.ts +16 -5
  90. package/src/engine-components/SceneSwitcher.ts +3 -0
  91. package/src/engine-components/api.ts +1 -0
  92. package/src/engine-components/codegen/components.ts +6 -6
  93. package/src/engine-components/timeline/PlayableDirector.ts +20 -20
  94. package/src/engine-components/timeline/TimelineBuilder.ts +277 -17
  95. package/src/engine-components/timeline/TimelineTracks.ts +24 -16
  96. package/src/engine-components/web/CursorFollow.ts +0 -1
@@ -1,10 +1,15 @@
1
1
  import { AnimationClip, Object3D } from "three";
2
+ import type { Light, Material, PerspectiveCamera } from "three";
2
3
 
3
4
  import type { Animator } from "../Animator.js";
4
5
  import type { AudioSource } from "../AudioSource.js";
5
6
  import { GameObject } from "../Component.js";
6
7
  import { InstantiateIdProvider } from "../../engine/engine_networking_instantiate.js";
7
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>;
8
13
  import { SignalAsset, SignalReceiver, SignalReceiverEvent } from "./SignalAsset.js";
9
14
  import type { PlayableDirector } from "./PlayableDirector.js";
10
15
  import { ClipExtrapolation, TrackType } from "./TimelineModels.js";
@@ -108,6 +113,7 @@ type BuilderTrack = {
108
113
  volume?: number;
109
114
  trackOffset?: TrackOffset;
110
115
  cursor: number; // current time position for auto-advancing
116
+ inlineTracks: TrackDescriptor[]; // accumulated by .track() calls, committed at boundaries
111
117
  };
112
118
 
113
119
  type PendingSignal = {
@@ -116,6 +122,129 @@ type PendingSignal = {
116
122
  callback: Function;
117
123
  };
118
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
+
119
248
  /**
120
249
  * A fluent builder for creating timeline assets ({@link TimelineAssetModel}) from code.
121
250
  *
@@ -137,6 +266,19 @@ type PendingSignal = {
137
266
  * director.play();
138
267
  * ```
139
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
+ *
140
282
  * @example Using install() with signal callbacks
141
283
  * ```ts
142
284
  * TimelineBuilder.create("WithSignals")
@@ -170,20 +312,37 @@ export class TimelineBuilder {
170
312
  * @param name - Name for the timeline asset
171
313
  * @param seed - Optional numeric seed for deterministic guid generation. Defaults to `Date.now()`.
172
314
  */
173
- static create(name?: string, seed?: number): TimelineBuilder {
315
+ static create(name?: string, seed?: number): TimelineBuilderBase {
174
316
  return new TimelineBuilder(name ?? "Timeline", seed);
175
317
  }
176
318
 
177
319
  // #region Track creation
178
320
 
179
321
  /**
180
- * Adds an animation track. Subsequent `.clip()` calls add animation clips to this track.
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
+ *
181
339
  * @param name - Display name for the track
182
340
  * @param binding - The Animator or Object3D to animate
183
341
  */
184
- animationTrack(name: string, binding?: Animator | Object3D | null): this {
342
+ animationTrack(name: string, binding?: Animator | Object3D | null): AnimationTrackBuilder {
343
+ this.commitInlineTracks();
185
344
  this._currentTrack = this.pushTrack(name, TrackType.Animation, binding ?? null);
186
- return this;
345
+ return this as unknown as AnimationTrackBuilder;
187
346
  }
188
347
 
189
348
  /**
@@ -192,10 +351,11 @@ export class TimelineBuilder {
192
351
  * @param binding - The AudioSource to play audio on (optional)
193
352
  * @param volume - Track volume multiplier (default: 1)
194
353
  */
195
- audioTrack(name: string, binding?: AudioSource | Object3D | null, volume?: number): this {
354
+ audioTrack(name: string, binding?: AudioSource | Object3D | null, volume?: number): AudioTrackBuilder {
355
+ this.commitInlineTracks();
196
356
  this._currentTrack = this.pushTrack(name, TrackType.Audio, binding ?? null);
197
357
  this._currentTrack.volume = volume;
198
- return this;
358
+ return this as unknown as AudioTrackBuilder;
199
359
  }
200
360
 
201
361
  /**
@@ -203,18 +363,20 @@ export class TimelineBuilder {
203
363
  * @param name - Display name for the track
204
364
  * @param binding - The Object3D to show/hide
205
365
  */
206
- activationTrack(name: string, binding?: Object3D | null): this {
366
+ activationTrack(name: string, binding?: Object3D | null): ActivationTrackBuilder {
367
+ this.commitInlineTracks();
207
368
  this._currentTrack = this.pushTrack(name, TrackType.Activation, binding ?? null);
208
- return this;
369
+ return this as unknown as ActivationTrackBuilder;
209
370
  }
210
371
 
211
372
  /**
212
373
  * Adds a control track. Subsequent `.clip()` calls control nested timelines or objects.
213
374
  * @param name - Display name for the track
214
375
  */
215
- controlTrack(name: string): this {
376
+ controlTrack(name: string): ControlTrackBuilder {
377
+ this.commitInlineTracks();
216
378
  this._currentTrack = this.pushTrack(name, TrackType.Control, null);
217
- return this;
379
+ return this as unknown as ControlTrackBuilder;
218
380
  }
219
381
 
220
382
  /**
@@ -222,18 +384,20 @@ export class TimelineBuilder {
222
384
  * @param name - Display name for the track
223
385
  * @param binding - The SignalReceiver component (optional — if using `.signal()` with callbacks, one is created automatically by {@link install})
224
386
  */
225
- signalTrack(name: string, binding?: SignalReceiver | Object3D | null): this {
387
+ signalTrack(name: string, binding?: SignalReceiver | Object3D | null): SignalTrackBuilder {
388
+ this.commitInlineTracks();
226
389
  this._currentTrack = this.pushTrack(name, TrackType.Signal, binding ?? null);
227
- return this;
390
+ return this as unknown as SignalTrackBuilder;
228
391
  }
229
392
 
230
393
  /**
231
394
  * Adds a marker track. Use `.marker()` to add markers.
232
395
  * @param name - Display name for the track
233
396
  */
234
- markerTrack(name: string): this {
397
+ markerTrack(name: string): MarkerTrackBuilder {
398
+ this.commitInlineTracks();
235
399
  this._currentTrack = this.pushTrack(name, TrackType.Marker, null);
236
- return this;
400
+ return this as unknown as MarkerTrackBuilder;
237
401
  }
238
402
 
239
403
  // #endregion
@@ -243,23 +407,40 @@ export class TimelineBuilder {
243
407
  /**
244
408
  * Adds a clip to the current track. The clip type must match the track type.
245
409
  *
246
- * - On an **animation track**: pass an `AnimationClip` and optional {@link AnimationClipOptions}
410
+ * - On an **animation track**: pass an `AnimationClip`, a {@link TrackDescriptor}, or a `TrackDescriptor[]` and optional {@link AnimationClipOptions}
247
411
  * - On an **audio track**: pass a clip URL (string) and {@link AudioClipOptions}
248
412
  * - On an **activation track**: pass {@link ActivationClipOptions}
249
413
  * - On a **control track**: pass an Object3D and {@link ControlClipOptions}
250
414
  */
251
415
  clip(asset: AnimationClip, options?: AnimationClipOptions): this;
416
+ clip(descriptor: TrackDescriptor, options?: AnimationClipOptions): this;
417
+ clip(descriptors: TrackDescriptor[], options?: AnimationClipOptions): this;
252
418
  clip(url: string, options: AudioClipOptions): this;
253
419
  clip(options: ActivationClipOptions): this;
254
420
  clip(sourceObject: Object3D, options: ControlClipOptions): this;
255
- clip(assetOrOptions: AnimationClip | string | Object3D | ActivationClipOptions, options?: AnimationClipOptions | AudioClipOptions | ControlClipOptions): this {
421
+ clip(assetOrOptions: AnimationClip | TrackDescriptor | TrackDescriptor[] | string | Object3D | ActivationClipOptions, options?: AnimationClipOptions | AudioClipOptions | ControlClipOptions): this {
256
422
  if (!this._currentTrack) throw new Error("TimelineBuilder: .clip() must be called after a track method (e.g. .animationTrack())");
423
+ this.commitInlineTracks();
257
424
 
258
425
  const track = this._currentTrack;
259
426
 
260
427
  switch (track.type) {
261
428
  case TrackType.Animation: {
262
- const animClip = assetOrOptions as AnimationClip;
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
+
263
444
  const opts = (options ?? {}) as AnimationClipOptions;
264
445
  const duration = opts.duration ?? animClip.duration;
265
446
  const start = opts.start ?? track.cursor;
@@ -459,6 +640,47 @@ export class TimelineBuilder {
459
640
  return this;
460
641
  }
461
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
+
462
684
  // #endregion
463
685
 
464
686
  /**
@@ -469,6 +691,7 @@ export class TimelineBuilder {
469
691
  * internally and also wires up the SignalReceiver on the director's GameObject.
470
692
  */
471
693
  build(): TimelineAssetModel {
694
+ this.commitInlineTracks();
472
695
  const tracks: TrackModel[] = this._tracks.map(t => {
473
696
  const track: TrackModel = {
474
697
  name: t.name,
@@ -555,10 +778,47 @@ export class TimelineBuilder {
555
778
  clips: [],
556
779
  markers: [],
557
780
  cursor: 0,
781
+ inlineTracks: [],
558
782
  };
559
783
  this._tracks.push(track);
560
784
  return track;
561
785
  }
562
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
+
563
823
  // #endregion
564
824
  }
@@ -4,13 +4,14 @@ import { isDevEnvironment } from "../../engine/debug/index.js";
4
4
  import { Context } from "../../engine/engine_setup.js";
5
5
  import type { Constructor } from "../../engine/engine_types.js";
6
6
  import { getParam, resolveUrl } from "../../engine/engine_utils.js";
7
- import { setObjectAnimated } from "../AnimationUtils.js";
8
7
  import { Animator } from "../Animator.js"
9
8
  import { AudioSource } from "../AudioSource.js";
10
9
  import { GameObject } from "../Component.js";
11
10
  import type { PlayableDirector } from "./PlayableDirector.js";
12
11
  import { SignalReceiver } from "./SignalAsset.js";
13
12
  import * as Models from "./TimelineModels.js";
13
+ import type { TimelineBuilder } from "./TimelineBuilder.js";
14
+ import { AnimationUtils } from "../../engine/engine_animation.js";
14
15
 
15
16
  const debug = getParam("debugtimeline");
16
17
 
@@ -18,7 +19,7 @@ const debug = getParam("debugtimeline");
18
19
  * A TrackHandler is responsible for evaluating a specific type of timeline track.
19
20
  * A timeline track can be an animation track, audio track, signal track, control track etc and is controlled by a {@link PlayableDirector}.
20
21
  */
21
- export abstract class TrackHandler {
22
+ export abstract class TimelineTrackHandler {
22
23
  director!: PlayableDirector;
23
24
  track!: Models.TrackModel;
24
25
 
@@ -136,7 +137,7 @@ class AnimationClipOffsetData {
136
137
  }
137
138
 
138
139
  // TODO: add support for clip clamp modes (loop, pingpong, clamp)
139
- export class AnimationTrackHandler extends TrackHandler {
140
+ export class TimelineAnimationTrack extends TimelineTrackHandler {
140
141
  /** @internal */
141
142
  models: Array<Models.ClipModel> = [];
142
143
  /** @internal */
@@ -178,7 +179,7 @@ export class AnimationTrackHandler extends TrackHandler {
178
179
  onStateChanged() {
179
180
  if (this._animator) {
180
181
  // We can not check the *isPlaying* state here because the timeline might be paused and evaluated by e.g. ScrollFollow
181
- setObjectAnimated(this._animator.gameObject, this, this.director.enabled && this.director.weight > 0);
182
+ AnimationUtils.setObjectAnimated(this._animator.gameObject, this, this.director.enabled && this.director.weight > 0);
182
183
  }
183
184
  }
184
185
 
@@ -262,7 +263,7 @@ export class AnimationTrackHandler extends TrackHandler {
262
263
  // which overrides the timeline
263
264
  this._animator = GameObject.getComponent(this.target, Animator) ?? null;
264
265
  if (this._animator) {
265
- setObjectAnimated(this._animator.gameObject, this, true);
266
+ AnimationUtils.setObjectAnimated(this._animator.gameObject, this, true);
266
267
  }
267
268
  }
268
269
 
@@ -605,7 +606,7 @@ declare type AudioClipModel = Models.ClipModel & { _didTriggerPlay: boolean };
605
606
  * - The director is paused (`director.pause()`)
606
607
  * - The director is disabled or destroyed
607
608
  */
608
- export class AudioTrackHandler extends TrackHandler {
609
+ export class TimelineAudioTrack extends TimelineTrackHandler {
609
610
 
610
611
  models: Array<AudioClipModel> = [];
611
612
  listener!: AudioListener;
@@ -814,7 +815,7 @@ export class AudioTrackHandler extends TrackHandler {
814
815
  private static _audioBuffers: Map<string, Promise<AudioBuffer | null>> = new Map();
815
816
 
816
817
  public static dispose() {
817
- AudioTrackHandler._audioBuffers.clear();
818
+ TimelineAudioTrack._audioBuffers.clear();
818
819
  }
819
820
 
820
821
  private handleAudioLoading(model: Models.ClipModel, audio: Audio): Promise<AudioBuffer | null> | null {
@@ -824,8 +825,8 @@ export class AudioTrackHandler extends TrackHandler {
824
825
  // TODO: maybe we should cache the loaders / buffers here by path
825
826
  const path = this.getAudioFilePath(model.asset.clip);
826
827
 
827
- if (AudioTrackHandler._audioBuffers.get(path)) {
828
- const promise = AudioTrackHandler._audioBuffers.get(path)!
828
+ if (TimelineAudioTrack._audioBuffers.get(path)) {
829
+ const promise = TimelineAudioTrack._audioBuffers.get(path)!
829
830
  promise.then((buffer) => {
830
831
  if (buffer) audio.setBuffer(buffer);
831
832
  });
@@ -845,17 +846,17 @@ export class AudioTrackHandler extends TrackHandler {
845
846
  resolve(null);
846
847
  });
847
848
  });
848
- AudioTrackHandler._audioBuffers.set(path, loadingPromise);
849
+ TimelineAudioTrack._audioBuffers.set(path, loadingPromise);
849
850
  return loadingPromise;
850
851
  }
851
852
  }
852
853
 
853
- export class MarkerTrackHandler extends TrackHandler {
854
+ export class TimelineMarkerTrack extends TimelineTrackHandler {
854
855
  models: Array<Models.MarkerModel & Record<string, any>> = [];
855
856
  needsSorting = true;
856
857
 
857
858
  *foreachMarker<T>(type: string | null = null) {
858
- if(this.needsSorting) this.sort();
859
+ if (this.needsSorting) this.sort();
859
860
  for (const model of this.models) {
860
861
  if (model && model.type === type) yield model as T;
861
862
  }
@@ -876,7 +877,7 @@ export class MarkerTrackHandler extends TrackHandler {
876
877
  }
877
878
  }
878
879
 
879
- export class SignalTrackHandler extends TrackHandler {
880
+ export class SignalTrackHandler extends TimelineTrackHandler {
880
881
  models: Models.SignalMarkerModel[] = [];
881
882
  didTrigger: boolean[] = [];
882
883
  receivers: Array<SignalReceiver | null> = [];
@@ -954,8 +955,15 @@ export class SignalTrackHandler extends TrackHandler {
954
955
  }
955
956
  }
956
957
 
957
-
958
- export class ActivationTrackHandler extends TrackHandler {
958
+ /**
959
+ * Handles activation (visibility) of bound objects for a timeline activation track.
960
+ *
961
+ * Each clip on the track defines a time range during which the bound objects should be active (visible).
962
+ * @see TimelineTrackHandler for details on how tracks and clips work in general, and how to mutate them at runtime.
963
+ * @see PlayableDirector for how to control timeline playback and time.
964
+ * @see TimelineBuilder for how to create and configure timelines and tracks in the editor.
965
+ */
966
+ export class TimelineActivationTrack extends TimelineTrackHandler {
959
967
 
960
968
  evaluate(time: number) {
961
969
  if (this.track.muted) return;
@@ -987,7 +995,7 @@ export class ActivationTrackHandler extends TrackHandler {
987
995
  }
988
996
 
989
997
 
990
- export class ControlTrackHandler extends TrackHandler {
998
+ export class TimelineControlTrack extends TimelineTrackHandler {
991
999
  models: Array<Models.ClipModel> = [];
992
1000
  timelines: Array<PlayableDirector | null> = [];
993
1001
 
@@ -178,7 +178,6 @@ export class CursorFollow extends Behaviour {
178
178
  * - Cursor that follows terrain or mesh surfaces
179
179
  *
180
180
  * **Important notes:**
181
- * - Requires objects in the scene to have colliders for raycasting to work
182
181
  * - Works best with {@link keepDistance} set to `false` to allow depth changes
183
182
  * - Can be combined with {@link damping} for smooth surface following
184
183
  * - The raycast uses the physics system's raycast functionality