lecodes-sdk 2.2.9 → 2.2.11
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/dist/global.d.ts +42 -1
- package/dist/types/canvas/Canvas.d.ts +6 -6
- package/dist/types/gl/Camera.d.ts +5 -2
- package/dist/types/gl/Node.d.ts +0 -7
- package/dist/types/gl/Scene.d.ts +6 -24
- package/dist/types/inject.d.ts +5 -1
- package/dist/types/plugin.d.ts +27 -0
- package/dist/types/plugins/gen/ar/sdk/ar.d.ts +228 -0
- package/dist/types/plugins/gen/ar/sdk/ar.gen.d.ts +73 -0
- package/dist/types/plugins/gen/ar/sdk/controls.d.ts +28 -0
- package/dist/types/plugins/gen/audio-recorder/sdk/audio-recorder.d.ts +43 -0
- package/dist/types/plugins/gen/audio-recorder/sdk/audio-recorder.gen.d.ts +47 -0
- package/dist/types/plugins/gen/geolocation/sdk/geolocation.gen.d.ts +6 -2
- package/dist/types/plugins/gen/speech/sdk/speech.d.ts +44 -0
- package/dist/types/plugins/gen/speech/sdk/speech.gen.d.ts +39 -0
- package/dist/types/runtime/media.d.ts +3 -2
- package/dist/types/runtime/permissions.d.ts +16 -0
- package/dist/types/version.d.ts +1 -1
- package/dist/types.json +1 -1
- package/package.json +1 -1
- package/prompts/ar.md +81 -48
- package/prompts/dist/2d-game.md +1567 -0
- package/prompts/dist/3d-app.md +1712 -0
- package/prompts/dist/ar-app.md +1585 -0
- package/prompts/dist/concept.md +49 -0
- package/prompts/dist/design.md +521 -0
- package/prompts/dist/ui-app.md +1175 -0
- package/prompts/index.md +3 -1
- package/src/animate/tween/Timeline.ts +175 -175
- package/src/bridges/device.d.ts +20 -6
- package/src/bridges/gl.d.ts +33 -19
- package/src/bridges/media.d.ts +3 -1
- package/src/canvas/Canvas.ts +6 -6
- package/src/compile/header.ts +11 -4
- package/src/gl/Camera.ts +5 -2
- package/src/gl/CameraPlace.ts +52 -52
- package/src/gl/Light.ts +257 -257
- package/src/gl/Mesh.ts +120 -120
- package/src/gl/Node.ts +0 -8
- package/src/gl/Scene.ts +14 -81
- package/src/gl/animation/DynamicBone.ts +482 -482
- package/src/gl/scenarios.ts +291 -291
- package/src/inject.ts +13 -1
- package/src/plugin.ts +45 -2
- package/src/plugins/gen/ar/contract.d.ts +118 -0
- package/src/plugins/gen/ar/sdk/ar.gen.ts +112 -0
- package/src/plugins/gen/ar/sdk/ar.ts +699 -0
- package/src/plugins/gen/ar/sdk/controls.ts +148 -0
- package/src/plugins/gen/audio-recorder/contract.d.ts +64 -0
- package/src/plugins/gen/audio-recorder/sdk/audio-recorder.gen.ts +76 -0
- package/src/plugins/gen/audio-recorder/sdk/audio-recorder.ts +113 -0
- package/src/plugins/gen/geolocation/contract.d.ts +7 -5
- package/src/plugins/gen/geolocation/sdk/geolocation.gen.ts +6 -2
- package/src/plugins/gen/speech/contract.d.ts +67 -0
- package/src/plugins/gen/speech/sdk/speech.gen.ts +64 -0
- package/src/plugins/gen/speech/sdk/speech.ts +104 -0
- package/src/runtime/media.ts +11 -4
- package/src/runtime/permissions.ts +40 -0
- package/src/server/auth/host.ts +1 -1
- package/src/ui/presentable.ts +5 -0
- package/src/ui/router.ts +4 -3
- package/src/version.ts +1 -1
- package/dist/types/gl/controls.d.ts +0 -19
- package/dist/types/plugins/permission.d.ts +0 -1
- package/src/gl/controls.ts +0 -95
- package/src/plugins/permission.ts +0 -8
package/prompts/index.md
CHANGED
|
@@ -34,4 +34,6 @@ implement what you can with the loaded ones.
|
|
|
34
34
|
// default export is a SceneHandle: load()/open()) + use/ref/make in the def,
|
|
35
35
|
// scenario aspects MoveTo/FollowPath/Spin/LookAt/PlayAnimation;
|
|
36
36
|
// *.editor.ts + EDITOR/InspectorUI are editor-plugin files — never edit or import them
|
|
37
|
-
// AR: ARScene
|
|
37
|
+
// AR: ARScene, Anchor, ImageAnchor, ARPlane — the "ar" plugin (app.json "plugins": { "ar": "*" }): camera
|
|
38
|
+
// passthrough, tracking, planes, hitTest, anchors (root / pose / image), light,
|
|
39
|
+
// captureFrame, placement gestures via addControls
|
|
@@ -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
|
+
}
|
package/src/bridges/device.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
// The device bridge (`_creatorDevice`): what the app runs on — platform facts, haptics, the motion
|
|
2
|
-
// sensor, the clipboard, the share sheet, the file picker, the
|
|
2
|
+
// sensor, the clipboard, the share sheet, the file picker, the OS permissions.
|
|
3
3
|
// Part of the host contract: every runtime implements this global (runtime/native in C++, the web
|
|
4
4
|
// runtimes in TS). A HOST bridge (`@host`, see ./app.d.ts): the native side is the `HostDevice`
|
|
5
5
|
// table of runtime/native/include/creator-pkg/host.gen.h, generated from this file. Data properties
|
|
@@ -70,11 +70,25 @@ declare global {
|
|
|
70
70
|
* error. Absent on hosts without a dialog (headless, CREATOR_HIDDEN runs): the SDK resolves
|
|
71
71
|
* "nothing picked". */
|
|
72
72
|
openFilePicker?(onComplete: (files: [id: number, name: string, size: number][] | null) => void, onReject: (err: Error) => void, multiple: boolean, accept?: string): void
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
73
|
+
|
|
74
|
+
// ---- Permissions -----------------------------------------------------------------------------
|
|
75
|
+
// The OS permissions, ONE door for the SDK (ARScene.prepare, the camera views), the apps
|
|
76
|
+
// (`permissions.query / request`) and the plugins' generated glue (`@permission` on a method of
|
|
77
|
+
// a contract: the native half asks through the host's same service before the call). `kind` is
|
|
78
|
+
// "camera" | "microphone" | "location" | "speech"; a state is "granted" | "denied" | "prompt"
|
|
79
|
+
// (never asked). Both slots are async because the browser's Permissions API is. Hosts without
|
|
80
|
+
// a dialog (headless, desktop) leave them null and the SDK answers "granted": nothing there
|
|
81
|
+
// stands between an app and a feature; the feature itself says whether it exists.
|
|
82
|
+
/** The state of `kind` without a prompt. `onReject`: an unknown kind. */
|
|
83
|
+
queryPermission?(kind: string, onComplete: (state: string) => void, onReject: (err: string) => void): void
|
|
84
|
+
/** Ask the OS for `kind` — the dialog when it was never asked (the OS decides whether a denied
|
|
85
|
+
* one is asked again) — and complete with the state after. Prompts are queued by the host: two
|
|
86
|
+
* callers get one dialog after another, one kind asked twice while its dialog is up gets one
|
|
87
|
+
* dialog and two answers. `onReject`: an unknown kind, or nowhere to present the dialog. */
|
|
88
|
+
requestPermission?(kind: string, onComplete: (state: string) => void, onReject: (err: string) => void): void
|
|
89
|
+
/** Open the app's own page of the system settings (where a denied permission is turned on).
|
|
90
|
+
* Absent where there is no such page (web). */
|
|
91
|
+
openSettings?(): void
|
|
78
92
|
}
|
|
79
93
|
}
|
|
80
94
|
|
package/src/bridges/gl.d.ts
CHANGED
|
@@ -41,9 +41,11 @@ declare global {
|
|
|
41
41
|
openScene(sceneId: u32): void
|
|
42
42
|
/** @custom */
|
|
43
43
|
closeScene(): void
|
|
44
|
-
/**
|
|
44
|
+
/** Throws — AR is the "ar" plugin since 2.3 (docs/plans/ar-plugin-plan.md decision 6); kept for ONE
|
|
45
|
+
* minor so a bundle of an older SDK says so instead of failing on a missing method, gone in 3.0.
|
|
46
|
+
* @custom */
|
|
45
47
|
launchAR(sceneId: u32, onComplete: () => void, onReject: () => void): void
|
|
46
|
-
/** @custom */
|
|
48
|
+
/** Throws, as launchAR. @custom */
|
|
47
49
|
stopAR(): void
|
|
48
50
|
/** @custom */
|
|
49
51
|
launchVR(sceneId: u32, onComplete: () => void, onReject: (err?: unknown) => void): void
|
|
@@ -231,6 +233,21 @@ declare global {
|
|
|
231
233
|
setLodBias?(bias: f64): void
|
|
232
234
|
setMaterialGlobalParameter(sceneId: u32, i: u32, x: f32, y: f32, z: f32, w: f32): void
|
|
233
235
|
getCameraFov(sceneId: u32, fovType: u8): f64
|
|
236
|
+
/** The pose of the scene's camera: its entity's WORLD matrix (column-major). What a host's gl facade
|
|
237
|
+
* sets for a plugin that tracks the device (AR); an app moves the Camera node instead. Optional. */
|
|
238
|
+
setCameraMatrix?(sceneId: u32, mat: Float32Array): void
|
|
239
|
+
/** A projection the host computes, as a matrix (column-major, OpenGL clip conventions) with the
|
|
240
|
+
* near / far it was built with: it replaces the scene's fov projection until
|
|
241
|
+
* clearCameraProjectionMatrix (an AR framework's camera intrinsics and display crop). Optional:
|
|
242
|
+
* the gl facade's setProjection is a no-op on a host without it. */
|
|
243
|
+
setCameraProjectionMatrix?(sceneId: u32, mat: Float32Array, near: f32, far: f32): void
|
|
244
|
+
clearCameraProjectionMatrix?(sceneId: u32): void
|
|
245
|
+
/** The scene's background: texture `textureId` behind everything, filling the viewport, through
|
|
246
|
+
* the affine map `uv` (6 floats, row-major [a b tx c d ty]) from the viewport's normalized
|
|
247
|
+
* coordinates (0,0 top-left) to the texture's; 0xFFFFFFFF = none (the sky comes back). The engine
|
|
248
|
+
* draws it (creator.h setSceneBackground); the runtime registers its built-in material from the
|
|
249
|
+
* materials archive. Optional. @custom */
|
|
250
|
+
setSceneBackground?(sceneId: u32, textureId: u32, uv: Float32Array): void
|
|
234
251
|
/** The scene camera's projection: VERTICAL fov in degrees + near/far clip distances (defaults
|
|
235
252
|
* 60 / 0.01 / 1000). Aspect stays host-owned — the host STORES these per scene and re-applies
|
|
236
253
|
* them whenever the viewport changes, so one call outlives every resize. Optional: hosts that
|
|
@@ -330,9 +347,11 @@ declare global {
|
|
|
330
347
|
setUniformArray(materialId: u32, uniform: string, value: Float32Array): void
|
|
331
348
|
setUniformNull(materialId: u32, uniform: string): void
|
|
332
349
|
|
|
333
|
-
/**
|
|
350
|
+
/** The scene's camera = this entity (every Scene's Camera attaches through it; the name is a
|
|
351
|
+
* relic of the AR it first served — it is not AR). @custom */
|
|
334
352
|
attachCameraToAR(sceneId: u32, cameraEntityId: u32, onProjectionChange: (mat: Float32Array, fov: number) => void): void
|
|
335
|
-
/**
|
|
353
|
+
/** The scene view's [width, height] in logical units — the units of the scene's touches on every
|
|
354
|
+
* host: the viewport over the density the host set (Camera.displaySize). @custom */
|
|
336
355
|
getDisplaySize(): Float32Array
|
|
337
356
|
|
|
338
357
|
setVisible(entityId: u32, visible: boolean): void
|
|
@@ -1106,11 +1125,11 @@ declare global {
|
|
|
1106
1125
|
|
|
1107
1126
|
hasMesh(entityId: u32): boolean
|
|
1108
1127
|
|
|
1109
|
-
/** @custom */
|
|
1128
|
+
/** Throws, as launchAR (decision 6 of docs/plans/ar-plugin-plan.md). @custom */
|
|
1110
1129
|
createARController(sceneId: u32, cameraId: u32, mode: string, onTrack: (entityId: number, track: boolean) => void): void
|
|
1111
|
-
/** @custom */
|
|
1130
|
+
/** Throws, as launchAR. @custom */
|
|
1112
1131
|
createRootAnchor(sceneId: u32): u32
|
|
1113
|
-
/** @custom */
|
|
1132
|
+
/** Throws, as launchAR. @custom */
|
|
1114
1133
|
createAnchor(sceneId: u32, physicalWidth: f64, systemId: bytes): u32
|
|
1115
1134
|
|
|
1116
1135
|
// Particles. All emitter/curve config crosses as ONE Float32Array of [tag, payloadLen,
|
|
@@ -1146,15 +1165,19 @@ declare global {
|
|
|
1146
1165
|
getNoise2D(noiseId: u32, x: f32, y: f32): f64
|
|
1147
1166
|
getNoise3D(noiseId: u32, x: f32, y: f32, z: f32): f64
|
|
1148
1167
|
|
|
1149
|
-
/**
|
|
1168
|
+
/** The scene's picture as a PNG File: the composite of its next frame (the scene and its overlays,
|
|
1169
|
+
* under the platform's UI — creator.h readPixels), encoded by the core, in the host's buffer store;
|
|
1170
|
+
* settled through the dispatch queue. Rejects when the scene is not the one presented, or it
|
|
1171
|
+
* changes before the frame. @custom */
|
|
1150
1172
|
captureImage(sceneId: number, onComplete: (bufferId: number, name: string, size: number) => void, onReject: () => void): void
|
|
1151
1173
|
}
|
|
1152
1174
|
}
|
|
1153
1175
|
|
|
1154
1176
|
/** The 3D HOST's side of the contract — what the engine needs from the platform around Filament
|
|
1155
1177
|
* (`HostGL` of creator-pkg/host.gen.h): the GL context to share, the scene view's lifecycle, image
|
|
1156
|
-
* decoding into textures,
|
|
1157
|
-
* slot but `createTexture` is optional — the runtime skips what a host does not fill.
|
|
1178
|
+
* decoding into textures, VR sessions. OPTIONAL as a whole (a UI-only build has no 3D); every
|
|
1179
|
+
* slot but `createTexture` is optional — the runtime skips what a host does not fill. AR is not
|
|
1180
|
+
* here: it is a plugin, which drives the engine through the host's gl facade (docs/plans/ar-plugin-plan.md).
|
|
1158
1181
|
* @host gl HostGL @optional */
|
|
1159
1182
|
export interface HostGL {
|
|
1160
1183
|
/** The host's GL context Filament shares with (a desktop master context, the XR context on Quest);
|
|
@@ -1173,18 +1196,9 @@ export interface HostGL {
|
|
|
1173
1196
|
createTexture(systemId: bytes, flags: u32, onComplete: (textureId: u32, width: i32, height: i32) => void, onReject: (err: string) => void): void
|
|
1174
1197
|
/** Import a media player's frames as an engine texture (`_creator.createMediaPlayerTexture`); 0 = none. */
|
|
1175
1198
|
createMediaPlayerTexture?(playerId: i32): i32
|
|
1176
|
-
/** Start an AR session for the scene; settle one callback through the dispatch bridge. */
|
|
1177
|
-
launchAR?(sceneId: u32, onComplete: () => void, onReject: (err: string) => void): void
|
|
1178
|
-
stopAR?(): void
|
|
1179
1199
|
/** Start a VR (OpenXR) session for the scene; absent = "VR is not supported on this device". */
|
|
1180
1200
|
launchVR?(sceneId: u32, onComplete: () => void, onReject: (err: string) => void): void
|
|
1181
1201
|
stopVR?(): void
|
|
1182
|
-
/** Bind the AR camera of `sceneId` to `cameraId` in `mode` ("world" / "face" / …). */
|
|
1183
|
-
createARController?(sceneId: u32, cameraId: u32, mode: string): void
|
|
1184
|
-
/** The AR root anchor entity of a scene. */
|
|
1185
|
-
createRootAnchor?(sceneId: u32, entityId: u32): void
|
|
1186
|
-
/** An image anchor: track the image in host buffer `systemId` (`physicalWidth` in metres) on `entityId`. */
|
|
1187
|
-
createAnchor?(sceneId: u32, entityId: u32, physicalWidth: f32, systemId: bytes): void
|
|
1188
1202
|
}
|
|
1189
1203
|
|
|
1190
1204
|
export {}
|
package/src/bridges/media.d.ts
CHANGED
|
@@ -18,7 +18,9 @@ import type { f32, f64, i32 } from "./types"
|
|
|
18
18
|
declare global {
|
|
19
19
|
/** @host media @optional */
|
|
20
20
|
var _creatorMedia: {
|
|
21
|
-
/** A player for `src` as an owned Handle (null = the host failed to create one).
|
|
21
|
+
/** A player for `src` as an owned Handle (null = the host failed to create one). `src` is a url
|
|
22
|
+
* or `"id:<bufferId>"` (a host buffer: a local asset, a File, a FetchResponse), which the host
|
|
23
|
+
* resolves. @custom */
|
|
22
24
|
createAudio(src: string): Handle | null
|
|
23
25
|
/** @custom */
|
|
24
26
|
createVideo(src: string): Handle | null
|
package/src/canvas/Canvas.ts
CHANGED
|
@@ -115,14 +115,14 @@ export class Canvas extends Recorder {
|
|
|
115
115
|
}
|
|
116
116
|
|
|
117
117
|
/** Decode an image source into a standalone `Bitmap` you can blit with `drawImage()` — either an
|
|
118
|
-
* `SvgSource(...)` (drawn by the core, painted by the host) or a `FetchResponse` holding
|
|
119
|
-
* image bytes (decoded; an SVG file is detected). This is async because the web host
|
|
120
|
-
* browser; `await` it ONCE, then `drawImage(bitmap, …)` is synchronous and the Bitmap
|
|
121
|
-
* returned Bitmap owns a native surface at the image's natural pixel size
|
|
122
|
-
* once nothing references the Bitmap (`destroy()` frees it early). Typical use:
|
|
118
|
+
* `SvgSource(...)` (drawn by the core, painted by the host) or a `FetchResponse` / `File` holding
|
|
119
|
+
* already-fetched image bytes (decoded; an SVG file is detected). This is async because the web host
|
|
120
|
+
* decodes with the browser; `await` it ONCE, then `drawImage(bitmap, …)` is synchronous and the Bitmap
|
|
121
|
+
* is reusable. The returned Bitmap owns a native surface at the image's natural pixel size
|
|
122
|
+
* (`pixelRatio` 1), freed once nothing references the Bitmap (`destroy()` frees it early). Typical use:
|
|
123
123
|
* const logo = await canvas.loadImage(SvgSource(svg)) // or: await fetch(url) → the response
|
|
124
124
|
* canvas.drawImage(logo, 0, 0) // sync, blit as often as you like */
|
|
125
|
-
loadImage(source: SvgSourceValue | FetchResponse): Promise<Bitmap> {
|
|
125
|
+
loadImage(source: SvgSourceValue | FetchResponse | File): Promise<Bitmap> {
|
|
126
126
|
// Discriminate without touching FetchResponse internals: only an SvgSource carries `.svg`.
|
|
127
127
|
const isSvg = typeof (source as SvgSourceValue).svg === "string"
|
|
128
128
|
const svg = isSvg ? (source as SvgSourceValue).svg : ""
|
package/src/compile/header.ts
CHANGED
|
@@ -51,7 +51,7 @@ export const buildHeader = (name: string, code: string, opts: HeaderOptions = {}
|
|
|
51
51
|
// bundle, and nothing 2D works without a Scene2D anyway.
|
|
52
52
|
if (code.includes("_creator2d.createScene")) header += `// 2d: enable\n`
|
|
53
53
|
|
|
54
|
-
if (code.
|
|
54
|
+
if (usesAR(code, opts.plugins)) header += `// ar: enable\n`
|
|
55
55
|
|
|
56
56
|
// Short, so with the engine lines: a host reads it before it registers anything.
|
|
57
57
|
if (opts.plugins?.length) header += `// plugins: ${opts.plugins.join(";")}\n`
|
|
@@ -82,9 +82,16 @@ export const buildHeader = (name: string, code: string, opts: HeaderOptions = {}
|
|
|
82
82
|
return header
|
|
83
83
|
}
|
|
84
84
|
|
|
85
|
-
/**
|
|
86
|
-
|
|
87
|
-
|
|
85
|
+
/** `// ar: enable` (the platform's backend reads it: an AR app is offered to the LeCodes app): AR is the
|
|
86
|
+
* "ar" plugin since 2.3 (docs/plans/ar-plugin-plan.md) — an app that lists it and makes a 3D scene.
|
|
87
|
+
* Its ARScene is the plugin's wrapper, whose calls name no engine member a bundle could be read for. */
|
|
88
|
+
const usesAR = (code: string, plugins: readonly string[] | undefined): boolean =>
|
|
89
|
+
!!plugins?.includes("ar") && code.includes("_creator.createScene")
|
|
90
|
+
|
|
91
|
+
/** Same engine signals the header uses, for callers that need the project kind (e.g. icon type).
|
|
92
|
+
* `plugins` = the app's (app.json), what tells an AR app. */
|
|
93
|
+
export const detectProjectKind = (code: string, plugins?: readonly string[]): "ar" | "ui" | "2d" | "3d" => {
|
|
94
|
+
if (usesAR(code, plugins)) return "ar"
|
|
88
95
|
if (code.includes("_creator2d.createScene")) return "2d"
|
|
89
96
|
if (!code.includes("_creator.")) return "ui"
|
|
90
97
|
return "3d"
|
package/src/gl/Camera.ts
CHANGED
|
@@ -173,12 +173,15 @@ export class Camera extends Node {
|
|
|
173
173
|
return this
|
|
174
174
|
}
|
|
175
175
|
|
|
176
|
+
/** The scene view's [width, height] in logical units — the units of the scene's touches
|
|
177
|
+
* (`clientX` / `clientY`) and of `getRay`, on every host; × `device.pixelRatio` for device px.
|
|
178
|
+
* [0, 0] before the scene has a viewport. */
|
|
176
179
|
get displaySize(): [number, number] {
|
|
177
180
|
const s = _creator.getDisplaySize()
|
|
178
181
|
return [ s[0], s[1] ]
|
|
179
182
|
}
|
|
180
183
|
|
|
181
|
-
/** World-space ray direction through a
|
|
184
|
+
/** World-space ray direction through a point of the scene view (logical units, as a touch's). */
|
|
182
185
|
getViewDirection(screenX: number, screenY: number): Vec3 {
|
|
183
186
|
if (this._sceneId < 0) throw new Error("Camera is not attached to a scene")
|
|
184
187
|
const v = new Float32Array(3)
|
|
@@ -186,7 +189,7 @@ export class Camera extends Node {
|
|
|
186
189
|
return new Vec3(v[0], v[1], v[2])
|
|
187
190
|
}
|
|
188
191
|
|
|
189
|
-
/** A pick ray from the camera through a
|
|
192
|
+
/** A pick ray from the camera through a point of the scene view (logical units, as a touch's). */
|
|
190
193
|
getRay(screenX: number, screenY: number): Ray {
|
|
191
194
|
return new Ray(this.worldPosition, this.getViewDirection(screenX, screenY))
|
|
192
195
|
}
|