lecodes-sdk 1.2.0 → 2.0.1
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/README.md +5 -2
- package/dist/editor.d.ts +12 -0
- package/dist/global.d.ts +4 -8
- package/dist/host.d.ts +2 -3
- package/dist/types/animate/tween/Animation.d.ts +0 -3
- package/dist/types/animate/tween/animateValue.d.ts +4 -2
- package/dist/types/animate/tween/easing.d.ts +8 -0
- package/dist/types/animate/tween/spec.d.ts +15 -7
- package/dist/types/audio/audio.d.ts +2 -1
- package/dist/types/canvas/Canvas.d.ts +40 -108
- package/dist/types/canvas/gen/cssColor.d.ts +17 -0
- package/dist/types/canvas/gen/recorder.d.ts +118 -0
- package/dist/types/canvas/gen/spec.d.ts +144 -0
- package/dist/types/core/color.d.ts +3 -1
- package/dist/types/core/pins.d.ts +18 -0
- package/dist/types/g2/Node2D.d.ts +5 -8
- package/dist/types/g2/Scene2D.d.ts +6 -2
- package/dist/types/gl/Foliage.d.ts +30 -7
- package/dist/types/gl/Light.d.ts +8 -0
- package/dist/types/gl/Lightmap.d.ts +13 -2
- package/dist/types/gl/Material.d.ts +17 -3
- package/dist/types/gl/Model.d.ts +6 -2
- package/dist/types/gl/Node.d.ts +3 -6
- package/dist/types/gl/Scene.d.ts +41 -13
- package/dist/types/gl/Texture.d.ts +1 -1
- package/dist/types/gl/animation/Locomotion.d.ts +8 -1
- package/dist/types/inject.d.ts +11 -11
- package/dist/types/inject.editor.d.ts +1 -0
- package/dist/types/net/core.d.ts +7 -0
- package/dist/types/plugin.d.ts +76 -0
- package/dist/types/plugins/gen/camera/sdk/camera.d.ts +24 -0
- package/dist/types/plugins/gen/camera/sdk/camera.gen.d.ts +25 -0
- package/dist/types/plugins/{geolocation.d.ts → gen/geolocation/sdk/geolocation.d.ts} +2 -20
- package/dist/types/plugins/gen/geolocation/sdk/geolocation.gen.d.ts +31 -0
- package/dist/types/plugins/{map.d.ts → gen/map/sdk/map.d.ts} +9 -50
- package/dist/types/plugins/gen/map/sdk/map.gen.d.ts +53 -0
- package/dist/types/plugins/gen/push/sdk/push.d.ts +23 -0
- package/dist/types/plugins/gen/push/sdk/push.gen.d.ts +35 -0
- package/dist/types/plugins/{qr.d.ts → gen/qr-scanner/sdk/qr-scanner.d.ts} +2 -3
- package/dist/types/plugins/gen/qr-scanner/sdk/qr-scanner.gen.d.ts +15 -0
- package/dist/types/runtime/app.d.ts +9 -2
- package/dist/types/runtime/fetch.d.ts +2 -0
- package/dist/types/runtime/input.d.ts +1 -1
- package/dist/types/runtime/media.d.ts +6 -10
- package/dist/types/runtime/misc.d.ts +4 -1
- package/dist/types/runtime/net.d.ts +3 -2
- package/dist/types/runtime/touch.d.ts +32 -0
- package/dist/types/scene/defineScene.d.ts +43 -2
- package/dist/types/scene/editor.d.ts +52 -0
- package/dist/types/scene/gizmos.d.ts +7 -4
- package/dist/types/ui/NativeView.d.ts +6 -4
- package/dist/types/ui/UI.d.ts +1 -1
- package/dist/types/ui/UIBottomSheet.d.ts +6 -12
- package/dist/types/ui/UIButton.d.ts +12 -14
- package/dist/types/ui/UIContainer.d.ts +0 -6
- package/dist/types/ui/UIImage.d.ts +1 -4
- package/dist/types/ui/UIInput.d.ts +8 -24
- package/dist/types/ui/UIModal.d.ts +0 -2
- package/dist/types/ui/UINode.d.ts +70 -56
- package/dist/types/ui/UIPager.d.ts +28 -27
- package/dist/types/ui/UIPopover.d.ts +0 -2
- package/dist/types/ui/UIScreen.d.ts +12 -17
- package/dist/types/ui/UIScrollable.d.ts +1 -4
- package/dist/types/ui/UIText.d.ts +0 -2
- package/dist/types/ui/UIVideo.d.ts +3 -5
- package/dist/types/ui/UIVirtualizedList.d.ts +14 -16
- package/dist/types/ui/UIWidget.d.ts +10 -9
- package/dist/types/ui/colorKeys.gen.d.ts +9 -0
- package/dist/types/ui/presentable.d.ts +46 -32
- package/dist/types/ui/router.d.ts +18 -7
- package/dist/types/ui/styleColor.d.ts +1 -0
- package/dist/types/ui/transitions.d.ts +18 -0
- package/dist/types/ui/tree.d.ts +75 -0
- package/dist/types/version.d.ts +10 -0
- package/dist/types.json +1 -1
- package/package.json +12 -3
- package/prompts/2d.md +2 -6
- package/prompts/3d.md +1 -5
- package/prompts/README.md +1 -1
- package/prompts/canvas.md +9 -8
- package/prompts/compose.ts +1 -1
- package/prompts/core.md +3 -3
- package/prompts/design.md +1 -1
- package/prompts/dist/2d-game.md +473 -239
- package/prompts/dist/3d-app.md +553 -205
- package/prompts/dist/ar-app.md +435 -202
- package/prompts/dist/design.md +113 -95
- package/prompts/dist/ui-app.md +386 -170
- package/prompts/ui-design.md +2 -3
- package/prompts/ui.md +45 -37
- package/src/animate/tween/Animation.ts +34 -150
- package/src/animate/tween/Timeline.ts +175 -175
- package/src/animate/tween/animateValue.ts +6 -3
- package/src/animate/tween/easing.ts +10 -3
- package/src/animate/tween/spec.ts +41 -15
- package/src/audio/Sound.ts +3 -3
- package/src/audio/audio.ts +2 -1
- package/src/bridges/2d.d.ts +317 -0
- package/src/bridges/app.d.ts +91 -0
- package/src/bridges/audio.d.ts +97 -0
- package/src/bridges/canvas.d.ts +79 -0
- package/src/bridges/device.d.ts +72 -0
- package/src/bridges/fetch.d.ts +80 -0
- package/src/bridges/files.d.ts +70 -0
- package/src/bridges/gl.d.ts +1133 -0
- package/src/bridges/input.d.ts +72 -0
- package/src/bridges/media.d.ts +55 -0
- package/src/bridges/nav.d.ts +71 -0
- package/src/bridges/net.d.ts +52 -0
- package/src/bridges/service.d.ts +52 -0
- package/src/bridges/socket.d.ts +31 -0
- package/src/bridges/storage.d.ts +33 -0
- package/src/bridges/tree.d.ts +301 -0
- package/src/bridges/types.d.ts +49 -0
- package/src/canvas/Canvas.ts +114 -159
- package/src/canvas/gen/cssColor.ts +224 -0
- package/src/canvas/gen/recorder.ts +212 -0
- package/src/canvas/gen/spec.ts +201 -0
- package/src/chisel.ts +193 -0
- package/src/compile/assetMacro.ts +1 -1
- package/src/compile/bundler.ts +11 -2
- package/src/compile/compileProject.ts +43 -4
- package/src/compile/fontMacro.ts +3 -4
- package/src/compile/header.ts +26 -5
- package/src/compile/index.ts +3 -1
- package/src/compile/liteMaterial.ts +1 -1
- package/src/compile/sceneEditor.ts +11 -26
- package/src/core/color.ts +73 -30
- package/src/core/pins.ts +51 -0
- package/src/core/signals.ts +8 -1
- package/src/g2/CharacterController2D.ts +3 -3
- package/src/g2/Node2D.ts +57 -39
- package/src/g2/Physics2D.ts +2 -2
- package/src/g2/Scene2D.ts +35 -23
- package/src/g2/Texture2D.ts +1 -1
- package/src/g2/loop.ts +4 -4
- package/src/gl/CameraPlace.ts +52 -52
- package/src/gl/Foliage.ts +72 -17
- package/src/gl/Geometry.ts +1 -2
- package/src/gl/Light.ts +10 -0
- package/src/gl/Lightmap.ts +45 -29
- package/src/gl/Material.ts +95 -51
- package/src/gl/Mesh.ts +120 -120
- package/src/gl/Model.ts +21 -16
- package/src/gl/Node.ts +91 -24
- package/src/gl/Particles.ts +1 -1
- package/src/gl/Scene.ts +100 -44
- package/src/gl/Texture.ts +8 -7
- package/src/gl/animation/AnimationClip.ts +1 -1
- package/src/gl/animation/DynamicBone.ts +482 -482
- package/src/gl/animation/Locomotion.ts +8 -3
- package/src/gl/nav/NavMesh.ts +3 -4
- package/src/gl/physics/Physics.ts +2 -2
- package/src/gl/physics/physicsEvents.ts +3 -3
- package/src/gl/scenarios.ts +291 -291
- package/src/gl/terrain/Terrain.ts +4 -5
- package/src/gl/touch.ts +14 -15
- package/src/host.d.ts +2 -3
- package/src/inject.editor.ts +7 -0
- package/src/inject.ts +13 -16
- package/src/net/core.ts +6 -5
- package/src/net/index.ts +1 -1
- package/src/net/replication.ts +1 -1
- package/src/plugin.ts +191 -0
- package/src/plugins/gen/camera/contract.d.ts +27 -0
- package/src/plugins/gen/camera/sdk/camera.gen.ts +46 -0
- package/src/plugins/gen/camera/sdk/camera.ts +57 -0
- package/src/plugins/gen/geolocation/contract.d.ts +50 -0
- package/src/plugins/gen/geolocation/sdk/geolocation.gen.ts +54 -0
- package/src/plugins/{geolocation.ts → gen/geolocation/sdk/geolocation.ts} +22 -43
- package/src/plugins/gen/map/contract.d.ts +144 -0
- package/src/plugins/gen/map/sdk/map.gen.ts +88 -0
- package/src/plugins/{map.ts → gen/map/sdk/map.ts} +68 -102
- package/src/plugins/gen/push/contract.d.ts +61 -0
- package/src/plugins/gen/push/sdk/push.gen.ts +60 -0
- package/src/plugins/gen/push/sdk/push.ts +105 -0
- package/src/plugins/gen/qr-scanner/contract.d.ts +16 -0
- package/src/plugins/gen/qr-scanner/sdk/qr-scanner.gen.ts +29 -0
- package/src/plugins/gen/qr-scanner/sdk/qr-scanner.ts +52 -0
- package/src/plugins/permission.ts +5 -4
- package/src/runtime/app.ts +20 -9
- package/src/runtime/appEvents.ts +5 -4
- package/src/runtime/channel.ts +18 -15
- package/src/runtime/clipboard.ts +4 -3
- package/src/runtime/datetime.ts +2 -1
- package/src/runtime/device.ts +17 -15
- package/src/runtime/fetch.ts +30 -20
- package/src/runtime/files.ts +16 -15
- package/src/runtime/input.ts +12 -10
- package/src/runtime/media.ts +50 -46
- package/src/runtime/misc.ts +7 -3
- package/src/runtime/net.ts +8 -7
- package/src/runtime/rpc.ts +1 -3
- package/src/runtime/service.ts +19 -14
- package/src/runtime/share.ts +4 -3
- package/src/runtime/storage.ts +6 -4
- package/src/runtime/touch.ts +32 -0
- package/src/scene/defineScene.ts +61 -365
- package/src/scene/editor.ts +408 -0
- package/src/scene/editorPlugins.ts +3 -3
- package/src/scene/gizmos.ts +15 -9
- package/src/server/db/marci/query.ts +1 -1
- package/src/server/host.ts +1 -1
- package/src/server/runtime.ts +1 -1
- package/src/ui/NativeView.ts +72 -25
- package/src/ui/UI.ts +3 -3
- package/src/ui/UIBottomSheet.ts +16 -17
- package/src/ui/UIButton.ts +54 -16
- package/src/ui/UIContainer.ts +0 -6
- package/src/ui/UIImage.ts +34 -30
- package/src/ui/UIInput.ts +29 -37
- package/src/ui/UIModal.ts +1 -3
- package/src/ui/UINode.ts +347 -297
- package/src/ui/UIPager.ts +93 -78
- package/src/ui/UIPopover.ts +0 -2
- package/src/ui/UIScreen.ts +41 -36
- package/src/ui/UIScrollable.ts +19 -13
- package/src/ui/UISpacer.ts +1 -1
- package/src/ui/UITabs.ts +8 -6
- package/src/ui/UIText.ts +7 -17
- package/src/ui/UIVideo.ts +30 -27
- package/src/ui/UIVirtualizedList.ts +58 -59
- package/src/ui/UIWidget.ts +38 -18
- package/src/ui/colorKeys.gen.ts +37 -0
- package/src/ui/fonts.ts +2 -2
- package/src/ui/presentable.ts +59 -42
- package/src/ui/router.ts +52 -35
- package/src/ui/styleColor.ts +56 -0
- package/src/ui/theme.ts +7 -6
- package/src/ui/transitions.ts +249 -0
- package/src/ui/tree.ts +346 -0
- package/src/version.ts +24 -0
- package/tests/helpers/engineWorld.ts +11 -0
- package/tests/helpers/fakeTree.ts +353 -0
- package/tests/helpers/hostStubs.ts +31 -0
- package/tests/helpers/index.ts +14 -0
- package/tests/helpers/memoryMarci.ts +124 -0
- package/tests/helpers/phases.ts +23 -0
- package/tests/helpers/preload.ts +18 -0
- package/tests/helpers/stubApp.ts +2 -0
- package/tests/helpers/stubDevice.ts +2 -0
- package/tests/helpers/stubFetch.ts +2 -0
- package/tests/helpers/stubInput.ts +2 -0
- package/dist/inject.js +0 -4629
- package/dist/types/core/registry.d.ts +0 -7
- package/dist/types/plugins/camera.d.ts +0 -25
- package/dist/types/plugins/push.d.ts +0 -46
- package/src/bridges.d.ts +0 -1769
- package/src/compile/__tests__/assetIconMacro.test.ts +0 -219
- package/src/compile/__tests__/assetMacro.test.ts +0 -100
- package/src/compile/__tests__/assetName.test.ts +0 -55
- package/src/compile/__tests__/compile.test.ts +0 -310
- package/src/compile/__tests__/detectEntry.test.ts +0 -151
- package/src/compile/__tests__/fontMacro.test.ts +0 -199
- package/src/compile/__tests__/serverSplit.test.ts +0 -27
- package/src/core/__tests__/stateMachine.test.ts +0 -132
- package/src/core/registry.ts +0 -23
- package/src/plugins/camera.ts +0 -81
- package/src/plugins/push.ts +0 -132
- package/src/plugins/qr.ts +0 -73
|
@@ -1,175 +1,175 @@
|
|
|
1
|
-
// `Timeline` (docs/timeline-plan.md §2.4): choreography over the keyframe core. Tracks on any
|
|
2
|
-
// number of targets (UI elements, 3D / 2D nodes, lights, cameras) at absolute positions, JS calls
|
|
3
|
-
// at times, labels, holds — one clock, one handle. Also the `animateTo` / `animateFrom` bag
|
|
4
|
-
// builder every target class delegates to (`tweenBag`).
|
|
5
|
-
|
|
6
|
-
import { TweenAnimation, type Animation } from "./Animation"
|
|
7
|
-
import { makeValueTrack, type AnimateOptions, type AnimateValue } from "./animateValue"
|
|
8
|
-
import { CLOCK_GAME, CLOCK_UI, bagProps, iterationsOf, makeTrack, type Track, type TweenMeta, type TweenSpec, type TweenTarget } from "./spec"
|
|
9
|
-
|
|
10
|
-
/** Vector-valued props: an array value is ONE keyframe unless it is an array of vectors. */
|
|
11
|
-
const VECTOR_PROPS = new Set(["position", "scale", "quaternion", "eulerAngles", "color"])
|
|
12
|
-
|
|
13
|
-
const clockOf = (clock: TweenMeta["clock"], fallback: number): number =>
|
|
14
|
-
clock === "game" ? CLOCK_GAME : clock === "ui" ? CLOCK_UI : fallback
|
|
15
|
-
|
|
16
|
-
/** @internal Build the tracks of one options bag on one target. `fromCurrent` = animateTo shape. */
|
|
17
|
-
export const bagTracks = (target: TweenTarget, bag: Record<string, unknown>, atMs: number, fromCurrent: boolean): Track[] => {
|
|
18
|
-
const meta = bag as TweenMeta
|
|
19
|
-
const tracks: Track[] = []
|
|
20
|
-
for (const [prop, value] of bagProps(bag)) {
|
|
21
|
-
const track = makeTrack(target, prop, value, meta, atMs, fromCurrent, VECTOR_PROPS.has(prop))
|
|
22
|
-
if (track) tracks.push(track)
|
|
23
|
-
}
|
|
24
|
-
return tracks
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
/** @internal The `animateTo` / `animateFrom` implementation: one bag → one animation, played now. */
|
|
28
|
-
export const tweenBag = (target: TweenTarget, bag: Record<string, unknown>, fromCurrent: boolean): Animation => {
|
|
29
|
-
const meta = bag as TweenMeta
|
|
30
|
-
const tracks = bagTracks(target, bag, 0, fromCurrent)
|
|
31
|
-
let durationMs = 0
|
|
32
|
-
for (const t of tracks) durationMs = Math.max(durationMs, t.atMs + t.durMs)
|
|
33
|
-
const spec: TweenSpec = {
|
|
34
|
-
clock: clockOf(meta.clock, target._tweenClock),
|
|
35
|
-
durationMs, delayMs: 0,
|
|
36
|
-
iterations: iterationsOf(meta.loop),
|
|
37
|
-
pingPong: meta.loopMode !== "restart",
|
|
38
|
-
rate: 1, tracks, calls: [],
|
|
39
|
-
}
|
|
40
|
-
return new TweenAnimation(spec).play()
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
export type TimelineOptions = {
|
|
44
|
-
/** `'ui'` (default) = wall time; `'game'` follows `Time.scale` and pauses with the game. */
|
|
45
|
-
clock?: "ui" | "game",
|
|
46
|
-
/** Repeat the whole timeline: `true` = forever, a number = cycles. */
|
|
47
|
-
loop?: boolean | number,
|
|
48
|
-
loopMode?: "restart" | "ping-pong",
|
|
49
|
-
/** Wait before the first play, ms. */
|
|
50
|
-
delay?: number,
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
/** Where a track goes: ms, a label, or `[label, offsetMs]`. Default = the current end (sequencing). */
|
|
54
|
-
export type TimelinePosition = number | string | [string, number]
|
|
55
|
-
|
|
56
|
-
export type TimelineAddOptions = {
|
|
57
|
-
at?: TimelinePosition,
|
|
58
|
-
/** With several targets: each starts this many ms after the previous one. */
|
|
59
|
-
stagger?: number,
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
/** A choreography: tracks on many targets at absolute times, JS calls, labels and holds, driven by
|
|
63
|
-
* one clock. Build it once, `play()` on every entrance — `play()` restarts from t = 0 and the pose
|
|
64
|
-
* at any time is fully defined by the tracks (the first keyframe of a track holds before it
|
|
65
|
-
* starts, the last one after it ends). */
|
|
66
|
-
export class TimelineImpl extends TweenAnimation {
|
|
67
|
-
private _labels = new Map<string, number>()
|
|
68
|
-
private _end = 0
|
|
69
|
-
|
|
70
|
-
constructor(options: TimelineOptions = {}) {
|
|
71
|
-
super({
|
|
72
|
-
clock: clockOf(options.clock, CLOCK_UI),
|
|
73
|
-
durationMs: 0,
|
|
74
|
-
delayMs: options.delay ?? 0,
|
|
75
|
-
iterations: iterationsOf(options.loop),
|
|
76
|
-
pingPong: options.loopMode !== "restart",
|
|
77
|
-
rate: 1, tracks: [], calls: [],
|
|
78
|
-
})
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
private _at(at: TimelinePosition | undefined): number {
|
|
82
|
-
if (at === undefined) return this._end
|
|
83
|
-
if (typeof at === "number") return at
|
|
84
|
-
const name = Array.isArray(at) ? at[0] : at
|
|
85
|
-
const offset = Array.isArray(at) ? at[1] : 0
|
|
86
|
-
const base = this._labels.get(name)
|
|
87
|
-
if (base === undefined) throw new Error(`Timeline: unknown label "${name}"`)
|
|
88
|
-
return base + offset
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
private _grow(endMs: number): void {
|
|
92
|
-
if (endMs > this._end) this._end = endMs
|
|
93
|
-
this._spec.durationMs = this._end
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
/** Animate `props` on one target or several (staggered), like `animateTo` — arrays are keyframes,
|
|
97
|
-
* `duration` / `easing` / `times` / `commit` apply. The bag's `delay` shifts the track after `at`. */
|
|
98
|
-
add(target: TweenTarget | TweenTarget[], props: Record<string, unknown> & TweenMeta, options: TimelineAddOptions = {}): this {
|
|
99
|
-
const at = this._at(options.at)
|
|
100
|
-
const targets = Array.isArray(target) ? target : [target]
|
|
101
|
-
const stagger = options.stagger ?? 0
|
|
102
|
-
targets.forEach((t, i) => {
|
|
103
|
-
const tracks = bagTracks(t, props, at + i * stagger, true)
|
|
104
|
-
for (const tr of tracks) { this._spec.tracks.push(tr); this._grow(tr.atMs + tr.durMs) }
|
|
105
|
-
})
|
|
106
|
-
return this
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
/** Like `add` with the `animateFrom` shape: the target's own state is the implicit last keyframe. */
|
|
110
|
-
addFrom(target: TweenTarget | TweenTarget[], props: Record<string, unknown> & TweenMeta, options: TimelineAddOptions = {}): this {
|
|
111
|
-
const at = this._at(options.at)
|
|
112
|
-
const targets = Array.isArray(target) ? target : [target]
|
|
113
|
-
const stagger = options.stagger ?? 0
|
|
114
|
-
targets.forEach((t, i) => {
|
|
115
|
-
const tracks = bagTracks(t, props, at + i * stagger, false)
|
|
116
|
-
for (const tr of tracks) { this._spec.tracks.push(tr); this._grow(tr.atMs + tr.durMs) }
|
|
117
|
-
})
|
|
118
|
-
return this
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
/** A free-value track (see the `animate()` global): the host evaluates it and calls `onUpdate`
|
|
122
|
-
* every frame with the value. */
|
|
123
|
-
animate<T extends AnimateValue>(options: AnimateOptions<T>, at?: TimelinePosition): this {
|
|
124
|
-
const { track, deliver, slot } = makeValueTrack(options, this._at(at))
|
|
125
|
-
this._values.set(slot, deliver)
|
|
126
|
-
this._spec.tracks.push(track)
|
|
127
|
-
this._grow(track.atMs + track.durMs)
|
|
128
|
-
return this
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
/** Call `fn` when playback crosses `at` (forward or backward), in time order, always before
|
|
132
|
-
* finish, never from a previous run. */
|
|
133
|
-
call(at: TimelinePosition, fn: () => void): this {
|
|
134
|
-
const t = this._at(at)
|
|
135
|
-
this._spec.calls.push(t)
|
|
136
|
-
this._calls.push(fn)
|
|
137
|
-
this._grow(t)
|
|
138
|
-
return this
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
/** Name a time — for `at`, `seek` and callers (`tl.labels.landed`). Default = the current end. */
|
|
142
|
-
label(name: string, at?: TimelinePosition): this {
|
|
143
|
-
this._labels.set(name, this._at(at))
|
|
144
|
-
return this
|
|
145
|
-
}
|
|
146
|
-
|
|
147
|
-
/** Named times, ms. */
|
|
148
|
-
get labels(): Record<string, number> {
|
|
149
|
-
const o: Record<string, number> = {}
|
|
150
|
-
for (const [k, v] of this._labels) o[k] = v
|
|
151
|
-
return o
|
|
152
|
-
}
|
|
153
|
-
|
|
154
|
-
/** Extend the timeline with stillness after its current end. */
|
|
155
|
-
hold(ms: number): this {
|
|
156
|
-
this._grow(this._end + Math.max(0, ms))
|
|
157
|
-
return this
|
|
158
|
-
}
|
|
159
|
-
|
|
160
|
-
/** The current end, ms (tracks, calls and holds). */
|
|
161
|
-
get end(): number { return this._end }
|
|
162
|
-
|
|
163
|
-
/** `seek` also accepts a label. */
|
|
164
|
-
override seek(at: number | string): this {
|
|
165
|
-
return super.seek(typeof at === "string" ? this._at(at) : at)
|
|
166
|
-
}
|
|
167
|
-
}
|
|
168
|
-
|
|
169
|
-
/** The timeline handle type (see {@link TimelineImpl} for the members). */
|
|
170
|
-
export type Timeline = TimelineImpl
|
|
171
|
-
|
|
172
|
-
/** Create a timeline — `Timeline()` / `Timeline({ clock: 'game', loop: true })`. */
|
|
173
|
-
export function Timeline(options?: TimelineOptions): Timeline {
|
|
174
|
-
return new TimelineImpl(options)
|
|
175
|
-
}
|
|
1
|
+
// `Timeline` (docs/timeline-plan.md §2.4): choreography over the keyframe core. Tracks on any
|
|
2
|
+
// number of targets (UI elements, 3D / 2D nodes, lights, cameras) at absolute positions, JS calls
|
|
3
|
+
// at times, labels, holds — one clock, one handle. Also the `animateTo` / `animateFrom` bag
|
|
4
|
+
// builder every target class delegates to (`tweenBag`).
|
|
5
|
+
|
|
6
|
+
import { TweenAnimation, type Animation } from "./Animation"
|
|
7
|
+
import { makeValueTrack, type AnimateOptions, type AnimateValue } from "./animateValue"
|
|
8
|
+
import { CLOCK_GAME, CLOCK_UI, bagProps, iterationsOf, makeTrack, type Track, type TweenMeta, type TweenSpec, type TweenTarget } from "./spec"
|
|
9
|
+
|
|
10
|
+
/** Vector-valued props: an array value is ONE keyframe unless it is an array of vectors. */
|
|
11
|
+
const VECTOR_PROPS = new Set(["position", "scale", "quaternion", "eulerAngles", "color"])
|
|
12
|
+
|
|
13
|
+
const clockOf = (clock: TweenMeta["clock"], fallback: number): number =>
|
|
14
|
+
clock === "game" ? CLOCK_GAME : clock === "ui" ? CLOCK_UI : fallback
|
|
15
|
+
|
|
16
|
+
/** @internal Build the tracks of one options bag on one target. `fromCurrent` = animateTo shape. */
|
|
17
|
+
export const bagTracks = (target: TweenTarget, bag: Record<string, unknown>, atMs: number, fromCurrent: boolean): Track[] => {
|
|
18
|
+
const meta = bag as TweenMeta
|
|
19
|
+
const tracks: Track[] = []
|
|
20
|
+
for (const [prop, value] of bagProps(bag)) {
|
|
21
|
+
const track = makeTrack(target, prop, value, meta, atMs, fromCurrent, VECTOR_PROPS.has(prop))
|
|
22
|
+
if (track) tracks.push(track)
|
|
23
|
+
}
|
|
24
|
+
return tracks
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/** @internal The `animateTo` / `animateFrom` implementation: one bag → one animation, played now. */
|
|
28
|
+
export const tweenBag = (target: TweenTarget, bag: Record<string, unknown>, fromCurrent: boolean): Animation => {
|
|
29
|
+
const meta = bag as TweenMeta
|
|
30
|
+
const tracks = bagTracks(target, bag, 0, fromCurrent)
|
|
31
|
+
let durationMs = 0
|
|
32
|
+
for (const t of tracks) durationMs = Math.max(durationMs, t.atMs + t.durMs)
|
|
33
|
+
const spec: TweenSpec = {
|
|
34
|
+
clock: clockOf(meta.clock, target._tweenClock),
|
|
35
|
+
durationMs, delayMs: 0,
|
|
36
|
+
iterations: iterationsOf(meta.loop),
|
|
37
|
+
pingPong: meta.loopMode !== "restart",
|
|
38
|
+
rate: 1, tracks, calls: [],
|
|
39
|
+
}
|
|
40
|
+
return new TweenAnimation(spec).play()
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export type TimelineOptions = {
|
|
44
|
+
/** `'ui'` (default) = wall time; `'game'` follows `Time.scale` and pauses with the game. */
|
|
45
|
+
clock?: "ui" | "game",
|
|
46
|
+
/** Repeat the whole timeline: `true` = forever, a number = cycles. */
|
|
47
|
+
loop?: boolean | number,
|
|
48
|
+
loopMode?: "restart" | "ping-pong",
|
|
49
|
+
/** Wait before the first play, ms. */
|
|
50
|
+
delay?: number,
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/** Where a track goes: ms, a label, or `[label, offsetMs]`. Default = the current end (sequencing). */
|
|
54
|
+
export type TimelinePosition = number | string | [string, number]
|
|
55
|
+
|
|
56
|
+
export type TimelineAddOptions = {
|
|
57
|
+
at?: TimelinePosition,
|
|
58
|
+
/** With several targets: each starts this many ms after the previous one. */
|
|
59
|
+
stagger?: number,
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/** A choreography: tracks on many targets at absolute times, JS calls, labels and holds, driven by
|
|
63
|
+
* one clock. Build it once, `play()` on every entrance — `play()` restarts from t = 0 and the pose
|
|
64
|
+
* at any time is fully defined by the tracks (the first keyframe of a track holds before it
|
|
65
|
+
* starts, the last one after it ends). */
|
|
66
|
+
export class TimelineImpl extends TweenAnimation {
|
|
67
|
+
private _labels = new Map<string, number>()
|
|
68
|
+
private _end = 0
|
|
69
|
+
|
|
70
|
+
constructor(options: TimelineOptions = {}) {
|
|
71
|
+
super({
|
|
72
|
+
clock: clockOf(options.clock, CLOCK_UI),
|
|
73
|
+
durationMs: 0,
|
|
74
|
+
delayMs: options.delay ?? 0,
|
|
75
|
+
iterations: iterationsOf(options.loop),
|
|
76
|
+
pingPong: options.loopMode !== "restart",
|
|
77
|
+
rate: 1, tracks: [], calls: [],
|
|
78
|
+
})
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
private _at(at: TimelinePosition | undefined): number {
|
|
82
|
+
if (at === undefined) return this._end
|
|
83
|
+
if (typeof at === "number") return at
|
|
84
|
+
const name = Array.isArray(at) ? at[0] : at
|
|
85
|
+
const offset = Array.isArray(at) ? at[1] : 0
|
|
86
|
+
const base = this._labels.get(name)
|
|
87
|
+
if (base === undefined) throw new Error(`Timeline: unknown label "${name}"`)
|
|
88
|
+
return base + offset
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
private _grow(endMs: number): void {
|
|
92
|
+
if (endMs > this._end) this._end = endMs
|
|
93
|
+
this._spec.durationMs = this._end
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/** Animate `props` on one target or several (staggered), like `animateTo` — arrays are keyframes,
|
|
97
|
+
* `duration` / `easing` / `times` / `commit` apply. The bag's `delay` shifts the track after `at`. */
|
|
98
|
+
add(target: TweenTarget | TweenTarget[], props: Record<string, unknown> & TweenMeta, options: TimelineAddOptions = {}): this {
|
|
99
|
+
const at = this._at(options.at)
|
|
100
|
+
const targets = Array.isArray(target) ? target : [target]
|
|
101
|
+
const stagger = options.stagger ?? 0
|
|
102
|
+
targets.forEach((t, i) => {
|
|
103
|
+
const tracks = bagTracks(t, props, at + i * stagger, true)
|
|
104
|
+
for (const tr of tracks) { this._spec.tracks.push(tr); this._grow(tr.atMs + tr.durMs) }
|
|
105
|
+
})
|
|
106
|
+
return this
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/** Like `add` with the `animateFrom` shape: the target's own state is the implicit last keyframe. */
|
|
110
|
+
addFrom(target: TweenTarget | TweenTarget[], props: Record<string, unknown> & TweenMeta, options: TimelineAddOptions = {}): this {
|
|
111
|
+
const at = this._at(options.at)
|
|
112
|
+
const targets = Array.isArray(target) ? target : [target]
|
|
113
|
+
const stagger = options.stagger ?? 0
|
|
114
|
+
targets.forEach((t, i) => {
|
|
115
|
+
const tracks = bagTracks(t, props, at + i * stagger, false)
|
|
116
|
+
for (const tr of tracks) { this._spec.tracks.push(tr); this._grow(tr.atMs + tr.durMs) }
|
|
117
|
+
})
|
|
118
|
+
return this
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
/** A free-value track (see the `animate()` global): the host evaluates it and calls `onUpdate`
|
|
122
|
+
* every frame with the value. */
|
|
123
|
+
animate<T extends AnimateValue>(options: AnimateOptions<T>, at?: TimelinePosition): this {
|
|
124
|
+
const { track, deliver, slot } = makeValueTrack(options, this._at(at))
|
|
125
|
+
this._values.set(slot, deliver)
|
|
126
|
+
this._spec.tracks.push(track)
|
|
127
|
+
this._grow(track.atMs + track.durMs)
|
|
128
|
+
return this
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
/** Call `fn` when playback crosses `at` (forward or backward), in time order, always before
|
|
132
|
+
* finish, never from a previous run. */
|
|
133
|
+
call(at: TimelinePosition, fn: () => void): this {
|
|
134
|
+
const t = this._at(at)
|
|
135
|
+
this._spec.calls.push(t)
|
|
136
|
+
this._calls.push(fn)
|
|
137
|
+
this._grow(t)
|
|
138
|
+
return this
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
/** Name a time — for `at`, `seek` and callers (`tl.labels.landed`). Default = the current end. */
|
|
142
|
+
label(name: string, at?: TimelinePosition): this {
|
|
143
|
+
this._labels.set(name, this._at(at))
|
|
144
|
+
return this
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
/** Named times, ms. */
|
|
148
|
+
get labels(): Record<string, number> {
|
|
149
|
+
const o: Record<string, number> = {}
|
|
150
|
+
for (const [k, v] of this._labels) o[k] = v
|
|
151
|
+
return o
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
/** Extend the timeline with stillness after its current end. */
|
|
155
|
+
hold(ms: number): this {
|
|
156
|
+
this._grow(this._end + Math.max(0, ms))
|
|
157
|
+
return this
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
/** The current end, ms (tracks, calls and holds). */
|
|
161
|
+
get end(): number { return this._end }
|
|
162
|
+
|
|
163
|
+
/** `seek` also accepts a label. */
|
|
164
|
+
override seek(at: number | string): this {
|
|
165
|
+
return super.seek(typeof at === "string" ? this._at(at) : at)
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
/** The timeline handle type (see {@link TimelineImpl} for the members). */
|
|
170
|
+
export type Timeline = TimelineImpl
|
|
171
|
+
|
|
172
|
+
/** Create a timeline — `Timeline()` / `Timeline({ clock: 'game', loop: true })`. */
|
|
173
|
+
export function Timeline(options?: TimelineOptions): Timeline {
|
|
174
|
+
return new TimelineImpl(options)
|
|
175
|
+
}
|
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
// evaluates natively and hands back to JS once per frame, the same easing vocabulary, keyframes,
|
|
5
5
|
// loops and clocks, and the same `Animation` handle. `Timeline.animate` puts one into a timeline.
|
|
6
6
|
|
|
7
|
+
import { LINEAR } from "./easing"
|
|
7
8
|
import { TweenAnimation, type Animation } from "./Animation"
|
|
8
9
|
import { Mat4 } from "../../math/mat4"
|
|
9
10
|
import type { Vec2Like, Vec3Like } from "../../math/vec"
|
|
@@ -15,8 +16,10 @@ import {
|
|
|
15
16
|
type Keyframe, type Track, type TweenChannel, type TweenMeta, type TweenSpec, type TweenTarget,
|
|
16
17
|
} from "./spec"
|
|
17
18
|
|
|
18
|
-
/** What `animate()` tweens: a number
|
|
19
|
-
* 0..1
|
|
19
|
+
/** What `animate()` tweens: a number (a FLOAT, never a packed color), a color (any CSS color string,
|
|
20
|
+
* through `Color` — delivered as rgba 0..1; a string that is not a color throws), a 2 / 3-vector (a
|
|
21
|
+
* 3-array is a vector, not a color), a quaternion (slerp), or a 4x4 matrix (position / rotation /
|
|
22
|
+
* scale). */
|
|
20
23
|
export type AnimateValue = number | string | Vec2Like | Vec3Like | QuatLike | Mat4Like | ColorInput
|
|
21
24
|
|
|
22
25
|
/** `onUpdate` receives a number for a number; every other kind arrives as the SAME `Float32Array`
|
|
@@ -65,7 +68,7 @@ export const makeValueTrack = (o: AnimateOptions<any>, atMs: number): { track: T
|
|
|
65
68
|
const kind = norm[0]!.kind
|
|
66
69
|
if (norm.some((n) => n!.kind !== kind)) throw new Error("animate: every value must be the same kind")
|
|
67
70
|
const lanes = lanesOf(kind)
|
|
68
|
-
const easing = easingFor(o.easing, "value")
|
|
71
|
+
const easing = easingFor(o.easing, "value", LINEAR) // a raw progress: the app maps it, the core never pre-eases it
|
|
69
72
|
const keys: Keyframe[] = norm.map((n, i) => ({
|
|
70
73
|
t: o.times && o.times.length === norm.length ? o.times[i]! : norm.length === 1 ? 1 : i / (norm.length - 1),
|
|
71
74
|
easing,
|
|
@@ -75,7 +75,7 @@ export const NAMED_EASINGS: ReadonlyArray<readonly [string, Easing]> = [
|
|
|
75
75
|
["inBounce", (x) => 1 - outBounce(1 - x)], // 25
|
|
76
76
|
["outBounce", outBounce], // 26
|
|
77
77
|
["inOutBounce", (x) => (x < 0.5 ? (1 - outBounce(1 - 2 * x)) / 2 : (1 + outBounce(2 * x - 1)) / 2)], // 27
|
|
78
|
-
["smoothstep", (x) => x * x * (3 - 2 * x)], // 28 — the $class
|
|
78
|
+
["smoothstep", (x) => x * x * (3 - 2 * x)], // 28 — the $class block default
|
|
79
79
|
]
|
|
80
80
|
|
|
81
81
|
/** Short spellings → canonical names. `in`/`out`/`inOut` are the quadratics, matching the SDK's
|
|
@@ -94,8 +94,15 @@ const namedId = (name: string): number => {
|
|
|
94
94
|
|
|
95
95
|
/** The CSS `ease` curve — what a bare `"ease"` means. */
|
|
96
96
|
const CSS_EASE: [number, number, number, number] = [0.25, 0.1, 0.25, 1]
|
|
97
|
-
|
|
98
|
-
|
|
97
|
+
/** The CSS `ease-in-out` curve — also UIKit's default animation curve, and web-lite's transition
|
|
98
|
+
* default: what every host tweened with before the keyframe core. */
|
|
99
|
+
export const CSS_EASE_IN_OUT: [number, number, number, number] = [0.42, 0, 0.58, 1]
|
|
100
|
+
|
|
101
|
+
export const LINEAR: EasingSpec = { kind: EASE_LINEAR, p: [0, 0, 0, 0] }
|
|
102
|
+
/** The default of every track with a TARGET (`animateTo` / `animateFrom` / `Timeline.add` on an
|
|
103
|
+
* element, a 3D / 2D object): `ease-in-out` (2026-09-27; linear before). `animate()` — a raw
|
|
104
|
+
* progress the app maps itself — stays linear. Write `easing: "linear"` for uniform motion. */
|
|
105
|
+
export const DEFAULT_EASING: EasingSpec = { kind: EASE_BEZIER, p: CSS_EASE_IN_OUT }
|
|
99
106
|
|
|
100
107
|
/** Sample an `Easing` function into a table. Endpoints are forced exact so a curve that is
|
|
101
108
|
* numerically 0.9999 at 1 still lands on the keyframe. */
|
|
@@ -19,7 +19,7 @@
|
|
|
19
19
|
// animation first plays (the implicit first key of `animateTo`, the implicit last of `animateFrom`).
|
|
20
20
|
// String-valued lanes (DISCRETE / STRING / TRANSFORM_MATRIX keys) hold a string-table index.
|
|
21
21
|
|
|
22
|
-
import { EASE_TABLE_SIZE, evalEasing, parseEasing, type EasingInput, type EasingSpec } from "./easing"
|
|
22
|
+
import { DEFAULT_EASING, EASE_TABLE_SIZE, evalEasing, parseEasing, type EasingInput, type EasingSpec } from "./easing"
|
|
23
23
|
import { Color } from "../../core/color"
|
|
24
24
|
import { Mat4 } from "../../math/mat4"
|
|
25
25
|
|
|
@@ -131,7 +131,8 @@ export type TweenMeta = {
|
|
|
131
131
|
delay?: number,
|
|
132
132
|
/** A curve for every prop, or a per-prop map (`{ width: 'outExpo', opacity: 'linear' }`). Names
|
|
133
133
|
* (`outCubic`, `inOutSine`, `outBack`, …), `cubic-bezier(…)` / a `[x1, y1, x2, y2]` tuple,
|
|
134
|
-
* `steps(n)`, or any `Easing` function. Default
|
|
134
|
+
* `steps(n)`, or any `Easing` function. Default `ease-in-out` (cubic-bezier(0.42, 0, 0.58, 1),
|
|
135
|
+
* the curve UIKit and CSS transitions ease with); `"linear"` for uniform motion. */
|
|
135
136
|
easing?: EasingInput | Record<string, EasingInput>,
|
|
136
137
|
/** Keyframe offsets 0..1 for array-valued props (default: evenly spaced). */
|
|
137
138
|
times?: number[],
|
|
@@ -158,12 +159,13 @@ export const warnOnce = (key: string, msg: string): void => {
|
|
|
158
159
|
console.warn(`[tween] ${msg}`)
|
|
159
160
|
}
|
|
160
161
|
|
|
161
|
-
/** Resolve the per-prop easing from the bag
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
162
|
+
/** Resolve the per-prop easing from the bag; nothing given (or no entry for `prop` in a per-prop
|
|
163
|
+
* map) = `fallback` — the tracks' DEFAULT_EASING, linear for a value tween. */
|
|
164
|
+
export const easingFor = (easing: TweenMeta["easing"], prop: string, fallback: EasingSpec = DEFAULT_EASING): EasingSpec => {
|
|
165
|
+
const one = easing !== undefined && typeof easing === "object" && !Array.isArray(easing) && typeof easing !== "function"
|
|
166
|
+
? (easing as Record<string, EasingInput>)[prop]
|
|
167
|
+
: easing as EasingInput | undefined
|
|
168
|
+
return one === undefined ? fallback : parseEasing(one)
|
|
167
169
|
}
|
|
168
170
|
|
|
169
171
|
/** Iteration count from `loop`: 1 = one-shot, -1 = forever, n = cycles. */
|
|
@@ -178,7 +180,8 @@ export const iterationsOf = (loop: TweenMeta["loop"]): number => {
|
|
|
178
180
|
const UNIT_RE = /^\s*(-?(?:\d+\.?\d*|\.\d+)(?:e[-+]?\d+)?)\s*(px|%|vw|vh|vmin|vmax|deg|rad|em|rem)?\s*$/i
|
|
179
181
|
|
|
180
182
|
/** A UI style value: number → FLOAT; "12px" / "50%" → FLOAT_UNIT; other strings → STRING (the host
|
|
181
|
-
* decides color vs discrete); booleans → DISCRETE.
|
|
183
|
+
* decides color vs discrete); booleans → DISCRETE. The element hands over the WIRE value
|
|
184
|
+
* (UINode._tweenChannel): a color key arrives as a string, never a packed number. */
|
|
182
185
|
export const uiValue = (v: unknown): ({ kind: number, sig?: string } & TweenValue) | null => {
|
|
183
186
|
if (typeof v === "number") return { kind: KIND_FLOAT, lanes: [v] }
|
|
184
187
|
if (typeof v === "boolean") return { kind: KIND_DISCRETE, str: v ? "true" : "false" }
|
|
@@ -195,15 +198,23 @@ export const uiValue = (v: unknown): ({ kind: number, sig?: string } & TweenValu
|
|
|
195
198
|
return null
|
|
196
199
|
}
|
|
197
200
|
|
|
198
|
-
/** Per-function argument counts of a UI transform list (degrees / px are the SDK's units).
|
|
201
|
+
/** Per-function argument counts of a UI transform list (degrees / px are the SDK's units). A
|
|
202
|
+
* translate named with a trailing `%` holds PERCENTS of the target's own box — a screen
|
|
203
|
+
* transition's pose (ui/transitions.ts), the only place that writes one. */
|
|
199
204
|
const TRANSFORM_FNS: Record<string, number> = {
|
|
200
205
|
translate: 2, translateX: 1, translateY: 1, rotate: 1, scale: 2, scaleX: 1, scaleY: 1, skewX: 1, skewY: 1,
|
|
206
|
+
"translate%": 2, "translateX%": 1, "translateY%": 1,
|
|
201
207
|
}
|
|
202
208
|
|
|
203
209
|
/** Parse `"translate(10px, 20px) rotate(45deg) scale(1.2)"` into a signature + lanes, or null when a
|
|
204
210
|
* function is unknown (the caller falls back to matrix decomposition on the host). `scale(2)` and
|
|
205
|
-
* `translate(5px)` complete their second argument like CSS.
|
|
206
|
-
|
|
211
|
+
* `translate(5px)` complete their second argument like CSS.
|
|
212
|
+
*
|
|
213
|
+
* `percent`: a `%` in a translate is a percent of the target's box (`translateX(100%)` →
|
|
214
|
+
* `translateX%` [100]; a translate mixing the units splits into its two axes). Without it a `%`
|
|
215
|
+
* reads as px, as the style's own parser reads it — with a warning: only a screen transition
|
|
216
|
+
* measures a translate in percents. */
|
|
217
|
+
export const parseTransformList = (s: string, percent = false): { sig: string, lanes: number[] } | null => {
|
|
207
218
|
const sig: string[] = []
|
|
208
219
|
const lanes: number[] = []
|
|
209
220
|
const re = /([a-zA-Z]+)\s*\(([^)]*)\)/g
|
|
@@ -215,15 +226,30 @@ export const parseTransformList = (s: string): { sig: string, lanes: number[] }
|
|
|
215
226
|
const fn = m[1]!
|
|
216
227
|
const n = TRANSFORM_FNS[fn]
|
|
217
228
|
if (n === undefined) return null
|
|
229
|
+
const units: string[] = []
|
|
218
230
|
const args = m[2]!.split(",").map((a) => {
|
|
219
231
|
const u = UNIT_RE.exec(a)
|
|
232
|
+
units.push(u ? (u[2] ?? "") : "")
|
|
220
233
|
return u ? parseFloat(u[1]!) : NaN
|
|
221
234
|
})
|
|
222
235
|
if (args.some((a) => Number.isNaN(a)) || args.length === 0 || args.length > n) return null
|
|
223
|
-
if (args.length < n) args.push(fn === "translate" ? 0 : args[0]!)
|
|
236
|
+
if (args.length < n) { args.push(fn === "translate" ? 0 : args[0]!); units.push(units[0]!) }
|
|
237
|
+
consumed = re.lastIndex
|
|
238
|
+
if (fn.startsWith("translate") && units.includes("%")) {
|
|
239
|
+
if (!percent) {
|
|
240
|
+
warnOnce("transform:%", `transform: a % in ${fn}() is read as px — percents of the box work in a screen transition only`)
|
|
241
|
+
} else if (fn === "translate" && units[0] !== units[1]) {
|
|
242
|
+
sig.push(units[0] === "%" ? "translateX%" : "translateX", units[1] === "%" ? "translateY%" : "translateY")
|
|
243
|
+
lanes.push(...args)
|
|
244
|
+
continue
|
|
245
|
+
} else {
|
|
246
|
+
sig.push(fn + "%")
|
|
247
|
+
lanes.push(...args)
|
|
248
|
+
continue
|
|
249
|
+
}
|
|
250
|
+
}
|
|
224
251
|
sig.push(fn)
|
|
225
252
|
lanes.push(...args)
|
|
226
|
-
consumed = re.lastIndex
|
|
227
253
|
}
|
|
228
254
|
if (src.slice(consumed).trim() !== "") return null
|
|
229
255
|
return { sig: sig.join(","), lanes }
|
|
@@ -428,7 +454,7 @@ export const evaluateTrack = (track: Track, ms: number, out: number[]): void =>
|
|
|
428
454
|
|
|
429
455
|
export type TweenBlob = { data: Float32Array, strings: string[], targets: unknown[] }
|
|
430
456
|
|
|
431
|
-
/** Flatten a spec for `
|
|
457
|
+
/** Flatten a spec for `_creatorTree.tweenCreate`. Tracks whose target has no host id right now are
|
|
432
458
|
* dropped (the animation still runs its clock, calls and finish). */
|
|
433
459
|
export const buildBlob = (spec: TweenSpec): TweenBlob => {
|
|
434
460
|
const strings: string[] = []
|
package/src/audio/Sound.ts
CHANGED
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
// gun.audio.play(shot) // 3D, through an AudioSource aspect
|
|
10
10
|
//
|
|
11
11
|
// A Sound is APP-scoped (the menu and the level share one `shot`); the engine drops every clip
|
|
12
|
-
// when `
|
|
12
|
+
// when `_creatorApp.run()` swaps worlds. Without host support `load` resolves to a silent clip.
|
|
13
13
|
|
|
14
14
|
import { fetch } from "../runtime/fetch"
|
|
15
15
|
import { audioSupported } from "./support"
|
|
@@ -48,7 +48,7 @@ export class Sound {
|
|
|
48
48
|
let channels = 1
|
|
49
49
|
for (const url of urls) {
|
|
50
50
|
const resp = await fetch(url, { useOnce: true })
|
|
51
|
-
const systemId = (resp as unknown as {
|
|
51
|
+
const systemId = (resp as unknown as { _h: Handle })._h.id
|
|
52
52
|
try {
|
|
53
53
|
const [ clipId, duration, ch ] = await new Promise<[number, number, number]>((resolve, reject) => {
|
|
54
54
|
_creatorAudio.loadClip(systemId, !!options.stereo,
|
|
@@ -59,7 +59,7 @@ export class Sound {
|
|
|
59
59
|
durations.push(duration)
|
|
60
60
|
channels = ch
|
|
61
61
|
} finally {
|
|
62
|
-
resp
|
|
62
|
+
// the response body goes with `resp` (owned handle)
|
|
63
63
|
}
|
|
64
64
|
}
|
|
65
65
|
return new Sound(urls, clips, durations, channels)
|
package/src/audio/audio.ts
CHANGED
|
@@ -81,7 +81,8 @@ class AudioSystem {
|
|
|
81
81
|
|
|
82
82
|
/** HRTF binaural rendering (MIT KEMAR filters on the CPU): sounds get a real up / down / behind in
|
|
83
83
|
* HEADPHONES. Off by default — on speakers it only smears the image. Applies to the `hrtfVoices`
|
|
84
|
-
* nearest 3D voices; the rest keep plain panning.
|
|
84
|
+
* nearest 3D voices; the rest keep plain panning. A host built without the filters (an iOS App
|
|
85
|
+
* Clip: they are 184 KB) keeps every voice on the panner — `stats.hrtfVoices` stays 0. */
|
|
85
86
|
get hrtf(): boolean { return this._hrtf }
|
|
86
87
|
set hrtf(v: boolean) { this._hrtf = v; if (audioSupported) _creatorAudio.setHrtf(v, this._hrtfVoices) }
|
|
87
88
|
/** `Time.scale` also pitches the sfx bus: slow motion drops every effect's tone, like a film. Default
|