lecodes-cli 0.18.2 → 0.19.2

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 (224) hide show
  1. package/dist/index.js +1866 -301
  2. package/package.json +14 -12
  3. package/runtime/scene-harness.json +1 -1
  4. package/runtime/sdk-types.json +1 -1
  5. package/src/api.ts +302 -0
  6. package/src/browserAuth.ts +87 -0
  7. package/src/cmgenTool.ts +104 -0
  8. package/src/commands/app.ts +891 -0
  9. package/src/commands/appAndroid.ts +603 -0
  10. package/src/commands/appDesktop.ts +326 -0
  11. package/src/commands/appDesktopMac.ts +470 -0
  12. package/src/commands/appIcon.ts +187 -0
  13. package/src/commands/appShared.ts +448 -0
  14. package/src/commands/appTemplates.ts +883 -0
  15. package/src/commands/appTemplatesAndroid.ts +599 -0
  16. package/src/commands/appTemplatesGradlew.ts +9 -0
  17. package/src/commands/assets.ts +28 -0
  18. package/src/commands/clone.ts +60 -0
  19. package/src/commands/compile.ts +253 -0
  20. package/src/commands/create.ts +59 -0
  21. package/src/commands/design.ts +863 -0
  22. package/src/commands/designTemplates.ts +11 -0
  23. package/src/commands/desktop.ts +122 -0
  24. package/src/commands/dev.ts +214 -0
  25. package/src/commands/diff.ts +59 -0
  26. package/src/commands/init.ts +191 -0
  27. package/src/commands/install.ts +198 -0
  28. package/src/commands/lightmap.ts +290 -0
  29. package/src/commands/link.ts +147 -0
  30. package/src/commands/login.ts +24 -0
  31. package/src/commands/navmesh.ts +225 -0
  32. package/src/commands/pn.ts +305 -0
  33. package/src/commands/projectTemplates.ts +129 -0
  34. package/src/commands/pull.ts +109 -0
  35. package/src/commands/push.ts +173 -0
  36. package/src/commands/render.ts +414 -0
  37. package/src/commands/scene.ts +148 -0
  38. package/src/commands/shaders.ts +157 -0
  39. package/src/commands/status.ts +29 -0
  40. package/src/commands/test.ts +370 -0
  41. package/src/commands/thumbs.ts +178 -0
  42. package/src/commands/types.ts +87 -0
  43. package/src/commands/update.ts +190 -0
  44. package/src/compile/assetIcons.ts +214 -0
  45. package/src/compile/collect.ts +46 -0
  46. package/src/compile/collectLocal.ts +37 -0
  47. package/src/compile/designCompile.ts +104 -0
  48. package/src/compile/fonts.ts +156 -0
  49. package/src/compile/headlessBundle.ts +129 -0
  50. package/src/compile/nativeStack.ts +21 -0
  51. package/src/compile/projectCompile.ts +109 -0
  52. package/src/compile/sceneCompile.ts +113 -0
  53. package/src/compile/screenEntry.ts +127 -0
  54. package/src/compile/shaders.ts +245 -0
  55. package/src/config.ts +42 -0
  56. package/src/designMeta.ts +35 -0
  57. package/src/desktopRenderer.ts +532 -0
  58. package/src/desktopScript.ts +276 -0
  59. package/src/dev/clientTemplates.ts +160 -0
  60. package/src/dev/devServer.ts +292 -0
  61. package/src/dev/wsServer.ts +144 -0
  62. package/src/distRoot.ts +20 -0
  63. package/src/ignore.ts +163 -0
  64. package/src/index.ts +491 -0
  65. package/src/lecodes-3d-editor.d.ts +41 -0
  66. package/src/lecodes-assets.d.ts +7 -0
  67. package/src/lecodes-design.d.ts +191 -0
  68. package/src/lecodes-renderer.d.ts +131 -0
  69. package/src/localFiles.ts +144 -0
  70. package/src/manifest.ts +46 -0
  71. package/src/matcTool.ts +137 -0
  72. package/src/peers.ts +40 -0
  73. package/src/project.ts +61 -0
  74. package/src/projectEnv.ts +94 -0
  75. package/src/qrcode-terminal.d.ts +9 -0
  76. package/src/releases.ts +125 -0
  77. package/src/serverDiff.ts +78 -0
  78. package/src/textDiff.ts +103 -0
  79. package/src/types.ts +0 -0
  80. package/src/util.ts +146 -0
  81. package/runtime/sdk/animate/animate.ts +0 -238
  82. package/runtime/sdk/animate/bezier.ts +0 -138
  83. package/runtime/sdk/animate/easings.ts +0 -126
  84. package/runtime/sdk/canvas/Canvas.ts +0 -305
  85. package/runtime/sdk/core/Aspect.ts +0 -512
  86. package/runtime/sdk/core/InspectorUI.ts +0 -212
  87. package/runtime/sdk/core/color.ts +0 -66
  88. package/runtime/sdk/core/compWrite.ts +0 -42
  89. package/runtime/sdk/core/events.ts +0 -38
  90. package/runtime/sdk/core/fields.ts +0 -120
  91. package/runtime/sdk/core/registry.ts +0 -23
  92. package/runtime/sdk/core/signals.ts +0 -277
  93. package/runtime/sdk/core/time.ts +0 -81
  94. package/runtime/sdk/g2/Camera2D.ts +0 -40
  95. package/runtime/sdk/g2/CharacterController2D.ts +0 -276
  96. package/runtime/sdk/g2/Node2D.ts +0 -267
  97. package/runtime/sdk/g2/OneWay2D.ts +0 -66
  98. package/runtime/sdk/g2/Physics2D.ts +0 -346
  99. package/runtime/sdk/g2/Scene2D.ts +0 -209
  100. package/runtime/sdk/g2/Shape2D.ts +0 -259
  101. package/runtime/sdk/g2/Sprite.ts +0 -89
  102. package/runtime/sdk/g2/SpriteAnimation.ts +0 -171
  103. package/runtime/sdk/g2/SpriteSheet.ts +0 -166
  104. package/runtime/sdk/g2/Texture2D.ts +0 -47
  105. package/runtime/sdk/g2/Tilemap.ts +0 -41
  106. package/runtime/sdk/g2/Tileset.ts +0 -71
  107. package/runtime/sdk/g2/Trigger2D.ts +0 -77
  108. package/runtime/sdk/g2/autotile.ts +0 -433
  109. package/runtime/sdk/g2/cells.ts +0 -91
  110. package/runtime/sdk/g2/defineScene2d.ts +0 -381
  111. package/runtime/sdk/g2/groups2d.ts +0 -106
  112. package/runtime/sdk/g2/loop.ts +0 -50
  113. package/runtime/sdk/g2/scenarios2d.ts +0 -69
  114. package/runtime/sdk/g2/touch.ts +0 -83
  115. package/runtime/sdk/gl/Camera.ts +0 -160
  116. package/runtime/sdk/gl/CameraPlace.ts +0 -52
  117. package/runtime/sdk/gl/CharacterController.ts +0 -238
  118. package/runtime/sdk/gl/Gearbox.ts +0 -212
  119. package/runtime/sdk/gl/Geometry.ts +0 -279
  120. package/runtime/sdk/gl/IK.ts +0 -193
  121. package/runtime/sdk/gl/InstancedMesh.ts +0 -132
  122. package/runtime/sdk/gl/Light.ts +0 -99
  123. package/runtime/sdk/gl/Lightmap.ts +0 -179
  124. package/runtime/sdk/gl/Material.ts +0 -245
  125. package/runtime/sdk/gl/Mesh.ts +0 -83
  126. package/runtime/sdk/gl/Model.ts +0 -64
  127. package/runtime/sdk/gl/Node.ts +0 -350
  128. package/runtime/sdk/gl/Noise.ts +0 -30
  129. package/runtime/sdk/gl/Particles.ts +0 -676
  130. package/runtime/sdk/gl/Physics.ts +0 -222
  131. package/runtime/sdk/gl/Plane.ts +0 -53
  132. package/runtime/sdk/gl/Ray.ts +0 -16
  133. package/runtime/sdk/gl/Scene.ts +0 -479
  134. package/runtime/sdk/gl/Shape.ts +0 -377
  135. package/runtime/sdk/gl/Texture.ts +0 -46
  136. package/runtime/sdk/gl/Trigger.ts +0 -45
  137. package/runtime/sdk/gl/Vehicle.ts +0 -473
  138. package/runtime/sdk/gl/Wheel.ts +0 -240
  139. package/runtime/sdk/gl/animation/AnimationClip.ts +0 -204
  140. package/runtime/sdk/gl/animation/Animator.ts +0 -87
  141. package/runtime/sdk/gl/animation/Layer.ts +0 -29
  142. package/runtime/sdk/gl/animation/Loop.ts +0 -25
  143. package/runtime/sdk/gl/animation/Playback.ts +0 -43
  144. package/runtime/sdk/gl/animation/core.ts +0 -294
  145. package/runtime/sdk/gl/controls.ts +0 -95
  146. package/runtime/sdk/gl/physicsEvents.ts +0 -20
  147. package/runtime/sdk/gl/scenarios.ts +0 -291
  148. package/runtime/sdk/gl/state.ts +0 -6
  149. package/runtime/sdk/gl/touch.ts +0 -68
  150. package/runtime/sdk/inject.ts +0 -186
  151. package/runtime/sdk/math/Mathf.ts +0 -118
  152. package/runtime/sdk/math/mat4.ts +0 -278
  153. package/runtime/sdk/math/quat.ts +0 -232
  154. package/runtime/sdk/math/vec.ts +0 -255
  155. package/runtime/sdk/plugins/camera.ts +0 -81
  156. package/runtime/sdk/plugins/geolocation.ts +0 -123
  157. package/runtime/sdk/plugins/oauth.ts +0 -61
  158. package/runtime/sdk/plugins/permission.ts +0 -7
  159. package/runtime/sdk/plugins/push.ts +0 -132
  160. package/runtime/sdk/plugins/qr.ts +0 -73
  161. package/runtime/sdk/plugins/service.ts +0 -47
  162. package/runtime/sdk/runtime/app.ts +0 -101
  163. package/runtime/sdk/runtime/appEvents.ts +0 -54
  164. package/runtime/sdk/runtime/channel.ts +0 -61
  165. package/runtime/sdk/runtime/clipboard.ts +0 -20
  166. package/runtime/sdk/runtime/datetime.ts +0 -329
  167. package/runtime/sdk/runtime/device.ts +0 -293
  168. package/runtime/sdk/runtime/fetch.ts +0 -77
  169. package/runtime/sdk/runtime/files.ts +0 -23
  170. package/runtime/sdk/runtime/input.ts +0 -175
  171. package/runtime/sdk/runtime/media.ts +0 -111
  172. package/runtime/sdk/runtime/misc.ts +0 -16
  173. package/runtime/sdk/runtime/net.ts +0 -36
  174. package/runtime/sdk/runtime/rpc.ts +0 -218
  175. package/runtime/sdk/runtime/service.ts +0 -83
  176. package/runtime/sdk/runtime/share.ts +0 -9
  177. package/runtime/sdk/runtime/storage.ts +0 -13
  178. package/runtime/sdk/runtime/touch.ts +0 -76
  179. package/runtime/sdk/scene/defineScene.ts +0 -1227
  180. package/runtime/sdk/scene/editorPlugins.ts +0 -92
  181. package/runtime/sdk/scene/gizmos.ts +0 -148
  182. package/runtime/sdk/scene/grammar.ts +0 -120
  183. package/runtime/sdk/scene/material.ts +0 -188
  184. package/runtime/sdk/server/auth/appConfig.ts +0 -12
  185. package/runtime/sdk/server/auth/global.ts +0 -80
  186. package/runtime/sdk/server/auth/host.ts +0 -318
  187. package/runtime/sdk/server/auth/models.ts +0 -83
  188. package/runtime/sdk/server/auth/types.ts +0 -50
  189. package/runtime/sdk/server/channel.ts +0 -56
  190. package/runtime/sdk/server/context.ts +0 -36
  191. package/runtime/sdk/server/db/defineDb.ts +0 -237
  192. package/runtime/sdk/server/db/fields.ts +0 -132
  193. package/runtime/sdk/server/db/httpTransport.ts +0 -93
  194. package/runtime/sdk/server/db/index.ts +0 -7
  195. package/runtime/sdk/server/db/marci/query.ts +0 -412
  196. package/runtime/sdk/server/db/types.ts +0 -202
  197. package/runtime/sdk/server/errors.ts +0 -12
  198. package/runtime/sdk/server/host.ts +0 -74
  199. package/runtime/sdk/server/inject.ts +0 -13
  200. package/runtime/sdk/server/runtime.ts +0 -133
  201. package/runtime/sdk/server/validate.ts +0 -87
  202. package/runtime/sdk/ui/NativeView.ts +0 -142
  203. package/runtime/sdk/ui/UI.ts +0 -39
  204. package/runtime/sdk/ui/UIBottomSheet.ts +0 -139
  205. package/runtime/sdk/ui/UIButton.ts +0 -101
  206. package/runtime/sdk/ui/UIContainer.ts +0 -60
  207. package/runtime/sdk/ui/UIImage.ts +0 -83
  208. package/runtime/sdk/ui/UIInput.ts +0 -185
  209. package/runtime/sdk/ui/UIModal.ts +0 -139
  210. package/runtime/sdk/ui/UINode.ts +0 -826
  211. package/runtime/sdk/ui/UIPager.ts +0 -362
  212. package/runtime/sdk/ui/UIPopover.ts +0 -100
  213. package/runtime/sdk/ui/UIScreen.ts +0 -123
  214. package/runtime/sdk/ui/UIScrollable.ts +0 -87
  215. package/runtime/sdk/ui/UISpacer.ts +0 -14
  216. package/runtime/sdk/ui/UITabs.ts +0 -236
  217. package/runtime/sdk/ui/UIText.ts +0 -51
  218. package/runtime/sdk/ui/UIVideo.ts +0 -88
  219. package/runtime/sdk/ui/UIVirtualizedList.ts +0 -241
  220. package/runtime/sdk/ui/UIWidget.ts +0 -127
  221. package/runtime/sdk/ui/fonts.ts +0 -13
  222. package/runtime/sdk/ui/presentable.ts +0 -117
  223. package/runtime/sdk/ui/router.ts +0 -132
  224. package/runtime/sdk/ui/theme.ts +0 -84
@@ -1,294 +0,0 @@
1
- // The engine-facing core behind Animator / Layer / Blend / Playback (docs/animator-plan.md). NOT exported
2
- // from the SDK: the public classes are thin views over the records here. The engine runs the whole
3
- // per-frame loop (fades, blend spaces, one-shot fade-back, root motion); this file binds clips,
4
- // issues play/stop/blend calls and routes the engine's slot events back to playbacks.
5
-
6
- import { AnimationClip } from "./AnimationClip"
7
- import { Loop } from "./Loop"
8
- import { Layer } from "./Layer"
9
- import { Playback } from "./Playback"
10
- import type { Node } from "../Node"
11
-
12
- /** A position on a blend axis (1D) or plane (2D). */
13
- export type BlendPosition = number | readonly [number, number]
14
-
15
- export type PlayOptions = {
16
- /** Crossfade seconds for BOTH ends (default 0 = cut). */
17
- fade?: number
18
- /** Fade-in seconds (overrides `fade`). */
19
- fadeIn?: number
20
- /** One-shots: fade-back seconds at the END (overrides `fade`). The return fade starts this long
21
- * BEFORE the clip ends, while it still plays — no freeze-frame on the last pose. On a layer with
22
- * no blend to return to, giving `fadeOut` releases the layer at the end instead of holding the
23
- * last frame. */
24
- fadeOut?: number
25
- /** Playback rate for this clip (default 1). */
26
- speed?: number
27
- /** Rewind even if the clip is already playing (default: rewind only if it isn't). */
28
- restart?: boolean
29
- }
30
-
31
- export type StopOptions = { fade?: number }
32
-
33
- export type LoopOptions = {
34
- /** Crossfade seconds from whatever the layer shows — the previous loop, or a one-shot still playing
35
- * (default 0 = cut). */
36
- fade?: number
37
- /** Playback rate of the loop's clips (default 1). */
38
- speed?: number
39
- }
40
- /** What a layer loops: one clip (name / index), or a blend space `{ name: position }`. */
41
- export type LoopDef = string | number | Record<string, BlendPosition>
42
-
43
- export type LayerOptions = {
44
- /** Bone name(s): the layer drives only these subtrees (`'Spine1'` = upper body). Default: the whole rig. */
45
- mask?: string | string[]
46
- /** Blend each clip's DELTA from its own first frame on top of the layers below (lean, breathe, recoil)
47
- * instead of overriding them. */
48
- additive?: boolean
49
- /** Layer contribution 0–1 (default 1). */
50
- weight?: number
51
- }
52
-
53
- export type ClipEventHandler = (clip: string, layer: Layer) => void
54
-
55
- // ---- records -------------------------------------------------------------------------------------
56
-
57
- export type SlotRec = { name: string, clip: AnimationClip, slot: number, layer: LayerRec, inBlend: boolean, playback?: PlaybackRec }
58
- export type PlaybackRec = { slot: SlotRec, playing: boolean, done: Promise<boolean>, resolve: (natural: boolean) => void }
59
- export type LayerRec = { index: number, view: Layer, slots: SlotRec[], byName: Map<string, SlotRec>, loop?: LoopRec, weight: number, mask: string, additive: boolean }
60
- export type LoopRec = { layer: LayerRec, members: { name: string, slot: SlotRec }[], value: BlendPosition, view: Loop, dead: boolean }
61
-
62
- const handlers = new Map<number, (slot: number, type: number) => void>()
63
- let eventsInstalled = false
64
- const ensureEvents = (): void => {
65
- if (eventsInstalled) return
66
- eventsInstalled = true
67
- _creator.setOnAnimatorEvent((animatorId, slot, type) => handlers.get(animatorId)?.(slot, type))
68
- }
69
-
70
- const settled = (slot: SlotRec): PlaybackRec => {
71
- let resolve = (_n: boolean): void => {}
72
- const done = new Promise<boolean>((r) => { resolve = r })
73
- resolve(false)
74
- return { slot, playing: false, done, resolve }
75
- }
76
-
77
- export class Core {
78
- id = 0
79
- /** Added clips by name (`addClip`) — the lookup index; an added name overrides an embedded one. */
80
- readonly table = new Map<string, AnimationClip>()
81
- readonly layers: LayerRec[] = []
82
- speed = 1
83
- private _embedded?: AnimationClip[]
84
- private readonly _added: { name: string, clip: AnimationClip }[] = []
85
- private _list?: AnimationClip[]
86
- private readonly _listeners = new Map<string, Set<ClipEventHandler>>()
87
-
88
- readonly node: Node
89
- constructor(node: Node) { this.node = node; this.layers.push(this.newLayer()) }
90
-
91
- // ---- clips ----
92
- embedded(): AnimationClip[] {
93
- if (!this._embedded) {
94
- this._embedded = AnimationClip._ofSet(_creator.getGlbClipSet(this.node.id))
95
- const seen = new Set<string>()
96
- for (const c of this._embedded) {
97
- if (seen.has(c.name)) console.warn(`Animator: ${this.node.name || "the node"} has several clips named '${c.name}' — by name you get the first; reach the others by index (anim.clip(n))`)
98
- seen.add(c.name)
99
- }
100
- }
101
- return this._embedded
102
- }
103
- /** The ordered clip list: the GLB's clips in file order (an added clip of the same name takes the
104
- * embedded one's place), then the remaining added clips in insertion order. Cached until addClip. */
105
- clips(): AnimationClip[] {
106
- if (this._list) return this._list
107
- const out: AnimationClip[] = []
108
- const placed = new Set<string>()
109
- for (const c of this.embedded()) {
110
- const override = this.table.get(c.name)
111
- if (override && !placed.has(c.name)) { out.push(override); placed.add(c.name) }
112
- else out.push(c)
113
- }
114
- for (const a of this._added) if (!placed.has(a.name)) { out.push(a.clip); placed.add(a.name) }
115
- return this._list = out
116
- }
117
- addClip(name: string, clip: AnimationClip): void {
118
- const i = this._added.findIndex((a) => a.name === name)
119
- if (i >= 0) this._added[i] = { name, clip }
120
- else this._added.push({ name, clip })
121
- this.table.set(name, clip)
122
- this._list = undefined
123
- }
124
- /** A clip by name (added first, then embedded), or by index into `clips()` (none = the first). */
125
- resolveClip(ref: string | number | undefined): [string, AnimationClip] | undefined {
126
- if (ref === undefined || typeof ref === "number") { const c = this.clips()[ref ?? 0]; return c ? [ c.name, c ] : undefined }
127
- const c = this.table.get(ref) ?? this.embedded().find((e) => e.name === ref)
128
- return c ? [ ref, c ] : undefined
129
- }
130
- private clipNames(): string { return this.clips().map((c) => c.name).join(", ") || "(empty)" }
131
-
132
- // ---- lifecycle ----
133
- /** Create the native animator on demand. False = nothing to animate (warned). */
134
- ensure(): boolean {
135
- if (this.id) return true
136
- if (this.embedded().length === 0 && this.table.size === 0) { console.warn(`Animator: no clips on ${this.node.name || "the node"} — nothing to play`); return false }
137
- ensureEvents()
138
- this.id = _creator.animatorCreate(this.node.id)
139
- if (!this.id) { console.warn("Animator: node has no transform hierarchy"); return false }
140
- handlers.set(this.id, (slot, type) => this.onEvent(slot, type))
141
- if (this.speed !== 1) _creator.animatorSetGlobal(this.id, this.speed, false)
142
- for (const L of this.layers) if (L.index > 0) this.pushLayer(L)
143
- return true
144
- }
145
- destroy(): void {
146
- for (const L of this.layers) for (const s of L.slots) if (s.playback) this.settle(s.playback, false)
147
- if (this.id) { _creator.animatorDestroy(this.id); handlers.delete(this.id); this.id = 0 }
148
- for (const L of this.layers) { L.slots = []; L.byName.clear(); if (L.loop) { L.loop.dead = true; L.loop = undefined } }
149
- }
150
-
151
- // ---- layers ----
152
- newLayer(options?: LayerOptions): LayerRec {
153
- const rec: LayerRec = { index: this.layers.length, view: undefined as unknown as Layer, slots: [], byName: new Map(), weight: options?.weight ?? 1, mask: "", additive: options?.additive ?? false }
154
- if (options?.mask !== undefined) rec.mask = Array.isArray(options.mask) ? options.mask.join("\n") : options.mask
155
- rec.view = new Layer(rec, this)
156
- return rec
157
- }
158
- addLayer(options: LayerOptions): Layer {
159
- if (this.layers.length >= 255) { console.warn("Animator: too many layers"); return this.layers[this.layers.length - 1].view }
160
- const rec = this.newLayer(options)
161
- this.layers.push(rec)
162
- if (this.id) this.pushLayer(rec)
163
- return rec.view
164
- }
165
- pushLayer(L: LayerRec): void {
166
- if (!this.id) return
167
- for (const m of L.mask.split("\n")) if (m && !this.node.bone(m)) console.warn(`Animator: layer mask bone '${m}' not found under ${this.node.name || "the node"}`)
168
- _creator.animatorSetLayer(this.id, L.index, L.weight, L.additive, L.mask)
169
- }
170
-
171
- // ---- slots ----
172
- /** Bind a clip on a layer (once per name). */
173
- slotFor(L: LayerRec, name: string, clip: AnimationClip): SlotRec | undefined {
174
- const have = L.byName.get(name)
175
- if (have && have.clip === clip) return have
176
- const slot = _creator.animatorBind(this.id, clip._set, clip._index, L.index)
177
- if (slot < 0) { console.warn(`Animator: could not bind clip '${name}'`); return undefined }
178
- const bound = _creator.animatorBoundTracks(this.id, slot)
179
- if (clip.trackCount > 0 && bound === 0) console.warn(`Animator: '${name}' — no track matched a node name under ${this.node.name || "the node"} (different rig?)`)
180
- else if (bound < clip.trackCount) console.warn(`Animator: '${name}' — ${clip.trackCount - bound}/${clip.trackCount} tracks unbound (bone names not found)`)
181
- const rec: SlotRec = { name, clip, slot, layer: L, inBlend: false }
182
- L.slots.push(rec)
183
- L.byName.set(name, rec)
184
- return rec
185
- }
186
-
187
- // ---- play / stop ----
188
- play(L: LayerRec, ref: string | number | undefined, o: PlayOptions): Playback {
189
- const found = this.resolveClip(ref)
190
- if (!found) {
191
- console.warn(`Animator: no clip ${ref === undefined ? "to play" : JSON.stringify(ref)} on ${this.node.name || "the node"} — table: ${this.clipNames()}`)
192
- return new Playback(settled({ name: "", clip: AnimationClip._none(), slot: -1, layer: L, inBlend: false }), this)
193
- }
194
- const [name, clip] = found
195
- if (!this.ensure()) return new Playback(settled({ name, clip, slot: -1, layer: L, inBlend: false }), this)
196
- const s = this.slotFor(L, name, clip)
197
- if (!s) return new Playback(settled({ name, clip, slot: -1, layer: L, inBlend: false }), this)
198
- if (s.inBlend) { console.warn(`Animator: '${name}' is part of this layer's loop — use playLoop() to change what it rests on`); return new Playback(settled(s), this) }
199
- const fadeIn = o.fadeIn ?? o.fade ?? 0
200
- const fadeOut = o.fadeOut ?? o.fade ?? -1
201
- // the other one-shots on this layer are cut short (their promises resolve false)
202
- for (const other of L.slots) if (other.playback) this.settle(other.playback, false)
203
- let resolve = (_n: boolean): void => {}
204
- const done = new Promise<boolean>((r) => { resolve = r })
205
- const rec: PlaybackRec = { slot: s, playing: true, done, resolve }
206
- s.playback = rec
207
- _creator.animatorPlay(this.id, s.slot, false, o.speed ?? 1, fadeIn, fadeOut, o.restart ?? false)
208
- return new Playback(rec, this)
209
- }
210
- /** A one-shot on the layer hasn't handed over yet. */
211
- busy(L: LayerRec): boolean { return L.slots.some((s) => s.playback?.playing) }
212
- settle(p: PlaybackRec, natural: boolean): void {
213
- p.playing = false
214
- if (p.slot.playback === p) p.slot.playback = undefined
215
- p.resolve(natural)
216
- }
217
- stopSlot(s: SlotRec, fade: number): void {
218
- if (s.playback) this.settle(s.playback, false)
219
- if (this.id && s.slot >= 0) _creator.animatorStop(this.id, s.layer.index, s.slot, fade)
220
- }
221
- stopLayer(L: LayerRec, fade: number): void {
222
- for (const s of L.slots) if (s.playback) this.settle(s.playback, false)
223
- if (L.loop) { L.loop.dead = true; L.loop = undefined }
224
- if (this.id) _creator.animatorStop(this.id, L.index, -1, fade)
225
- }
226
- stopAll(fade: number): void { for (const L of this.layers) this.stopLayer(L, fade) }
227
-
228
- // ---- loop ----
229
- loop(L: LayerRec, def: LoopDef, o: LoopOptions): Loop | undefined {
230
- if (!this.ensure()) return undefined
231
- const fade = o.fade ?? 0
232
- if (L.loop) { L.loop.dead = true; for (const m of L.loop.members) m.slot.inBlend = false; L.loop = undefined }
233
- for (const s of L.slots) if (s.playback) this.settle(s.playback, false) // one-shots on the layer are cut short
234
- const space: Record<string, BlendPosition> = typeof def === "object" ? def : {}
235
- if (typeof def !== "object") {
236
- const found = this.resolveClip(def)
237
- if (!found) { console.warn(`Animator: no clip ${JSON.stringify(def)} to loop — table: ${this.clipNames()}`); return undefined }
238
- space[found[0]] = 0
239
- }
240
- const rec: LoopRec = { layer: L, members: [], value: 0, view: undefined as unknown as Loop, dead: false }
241
- rec.view = new Loop(rec, this)
242
- const slots: number[] = [], positions: number[] = []
243
- const is2D = Object.values(space).some((p) => typeof p !== "number")
244
- for (const [name, at] of Object.entries(space)) {
245
- const found = this.resolveClip(name)
246
- if (!found) { console.warn(`Animator: loop clip '${name}' is not in the clip table`); continue }
247
- const s = this.slotFor(L, name, found[1])
248
- if (!s) continue
249
- if (s.playback) this.settle(s.playback, false)
250
- s.inBlend = true
251
- rec.members.push({ name, slot: s })
252
- slots.push(s.slot)
253
- if (is2D) positions.push(typeof at === "number" ? at : at[0], typeof at === "number" ? 0 : at[1])
254
- else positions.push(at as number)
255
- }
256
- L.loop = rec
257
- _creator.animatorSetBlend(this.id, L.index, is2D ? 2 : 1, new Uint16Array(slots), new Float32Array(positions), fade, o.speed ?? 1)
258
- return rec.view
259
- }
260
- setLoopValue(b: LoopRec, v: BlendPosition): void {
261
- if (b.dead) { console.warn("Animator: this loop was replaced — use the object the latest playLoop() returned"); return }
262
- b.value = v
263
- if (!this.id) return
264
- const [x, y] = typeof v === "number" ? [ v, 0 ] : [ v[0], v[1] ]
265
- _creator.animatorSetBlendValue(this.id, b.layer.index, x, y)
266
- }
267
- slotWeight(s: SlotRec): number { return this.id && s.slot >= 0 ? _creator.animatorGetSlotWeight(this.id, s.slot) : 0 }
268
- slotTime(s: SlotRec): number { return this.id && s.slot >= 0 ? _creator.animatorGetSlotTime(this.id, s.slot) : 0 }
269
- seek(s: SlotRec, time: number): void { if (this.id && s.slot >= 0) _creator.animatorSeek(this.id, s.slot, time) }
270
-
271
- // ---- props ----
272
- setSpeed(v: number): void { this.speed = v; if (this.id) _creator.animatorSetGlobal(this.id, v, false) }
273
- setRootMotion(on: boolean): void { if (this.ensure()) _creator.animatorSetRootMotion(this.id, on ? "*" : "", on ? 2 : 0) }
274
-
275
- // ---- events ----
276
- on(event: string, cb: ClipEventHandler): void { (this._listeners.get(event) ?? this._listeners.set(event, new Set()).get(event)!).add(cb) }
277
- off(event: string, cb: ClipEventHandler): void { this._listeners.get(event)?.delete(cb) }
278
- /** Native slot event: 0 completed / 1 loop / 2 settled (faded to 0) / 3 hand-over / 4+i clip event i. */
279
- private onEvent(slotIndex: number, type: number): void {
280
- let s: SlotRec | undefined
281
- for (const L of this.layers) { s = L.slots.find((x) => x.slot === slotIndex); if (s) break }
282
- if (!s) return
283
- if (type >= 4) {
284
- const name = s.clip._events[type - 4]?.name
285
- const cbs = name ? this._listeners.get(name) : undefined
286
- if (cbs) for (const cb of [ ...cbs ]) cb(s.name, s.layer.view)
287
- return
288
- }
289
- const p = s.playback
290
- if (!p) return
291
- if (type === 3) this.settle(p, true) // hand-over: the clip is over; the app's next call redirects the fade
292
- else if (type === 2) this.settle(p, false) // faded out after a stop / replacement before handing over
293
- }
294
- }
@@ -1,95 +0,0 @@
1
- // AR object-placement controls — the classic "drop a model in the room and manipulate it" gesture
2
- // recipe: ONE finger drags the target across the ground plane (camera-relative), TWO fingers pinch to
3
- // scale and twist to rotate (yaw). Lives on ARScene (see Scene.ts) because it orchestrates the scene's
4
- // camera + touch input against a target node; it's built entirely on the public API (no native calls
5
- // the user couldn't make), so it's a convenience, not a primitive.
6
-
7
- import { Vec2, Vec3 } from "../math/vec"
8
- import { Quat } from "../math/quat"
9
- import type { Node } from "./Node"
10
- import type { Scene } from "./Scene"
11
- import type { TouchStartEvent } from "../runtime/touch"
12
-
13
- export type ControlsOptions = {
14
- /** Smallest uniform scale the pinch gesture allows (default 0.08). */
15
- minScale?: number
16
- /** Largest uniform scale the pinch gesture allows (default 2). */
17
- maxScale?: number
18
- /** One-finger drag moves the target across the ground plane (default true). */
19
- pan?: boolean
20
- /** Two-finger pinch scales the target (default true). */
21
- pinch?: boolean
22
- /** Two-finger twist rotates the target around its Y axis (default true). */
23
- twist?: boolean
24
- }
25
-
26
- /** Returned by `addControls` — call `remove()` to detach the gesture listeners. */
27
- export type ControlsHandle = { remove(): void }
28
-
29
- type ActiveTouch = { pointerId: number, x: number, y: number }
30
-
31
- const clamp = (v: number, lo: number, hi: number): number => (v < lo ? lo : v > hi ? hi : v)
32
-
33
- export const attachControls = (scene: Scene, target: Node, options: ControlsOptions = {}): ControlsHandle => {
34
- const minScale = options.minScale ?? 0.08
35
- const maxScale = options.maxScale ?? 2
36
- const pan = options.pan ?? true
37
- const pinch = options.pinch ?? true
38
- const twist = options.twist ?? true
39
-
40
- const active: ActiveTouch[] = []
41
-
42
- const onTouchStart = (e: TouchStartEvent<Node | null>): void => {
43
- const touch: ActiveTouch = { pointerId: e.pointerId, x: e.clientX, y: e.clientY }
44
- active.push(touch)
45
- e.track({
46
- onMove(m) {
47
- if (active.length === 2) {
48
- // --- two-finger: pinch-scale + twist-rotate about the gesture axis ---
49
- const other = active.find((t) => t !== touch)
50
- if (other) {
51
- const axis = new Vec2(other.x - touch.x, other.y - touch.y)
52
- const span = axis.length()
53
- if (span > 1e-3) {
54
- const dir = axis.scale(1 / span)
55
- const delta = new Vec2(m.deltaX, m.deltaY)
56
- if (pinch) {
57
- const along = -dir.dot(delta) // movement along the pinch axis
58
- target.scale = clamp(target.scale.x * (span + along) / span, minScale * 0.4, maxScale * 1.5)
59
- }
60
- if (twist) {
61
- const tangent = dir.perp().dot(delta) // movement across the axis
62
- target.quaternion = target.quaternion.mul(Quat.fromAxisAngle(Vec3.up, Math.atan2(tangent, span)))
63
- }
64
- }
65
- }
66
- } else if (active.length === 1 && pan) {
67
- // --- one-finger: drag across the ground plane, relative to where the camera looks ---
68
- let dir = scene.camera.forward
69
- const parent = target.parent
70
- if (parent) dir = dir.rotate(parent.quaternion.invert()) // into the parent's local frame
71
- dir = dir.withY(0)
72
- const k = Math.hypot(dir.x, dir.z)
73
- const screen = Math.max(...scene.camera.displaySize)
74
- if (k > 1e-4 && screen > 0) {
75
- const dist = target.worldPosition.distanceTo(scene.camera.worldPosition)
76
- const right = new Vec3(-dir.z, 0, dir.x).scale(m.deltaX / k / screen * dist)
77
- const fwd = dir.scale(-m.deltaY / k / screen * dist)
78
- const move = right.add(fwd)
79
- const p = target.position
80
- target.position = new Vec3(p.x + move.x, p.y, p.z + move.z) // keep height fixed
81
- }
82
- }
83
- touch.x = m.clientX
84
- touch.y = m.clientY
85
- },
86
- onEnd() {
87
- const i = active.indexOf(touch)
88
- if (i >= 0) active.splice(i, 1)
89
- },
90
- })
91
- }
92
-
93
- scene.addEventListener("touchstart", onTouchStart)
94
- return { remove() { scene.removeEventListener("touchstart", onTouchStart); active.length = 0 } }
95
- }
@@ -1,20 +0,0 @@
1
- // Routes native physics contact/sensor events to the owning nodes' 'enter'/'exit' events. Each event
2
- // is delivered to BOTH nodes with the other as the argument. type: 0 = enter (contact/overlap began),
3
- // 1 = exit (ended). Registered lazily on the first trigger/physics body or 'enter'/'exit' listener.
4
-
5
- import { nodeRegistry } from "./Node"
6
-
7
- let registered = false
8
- export const ensurePhysicsEvents = (): void => {
9
- if (registered) return
10
- if (!_creator.setOnPhysicsEvent) return // physics-less build / bridge without the hook
11
- registered = true
12
- _creator.setOnPhysicsEvent((a, b, type) => {
13
- const na = nodeRegistry.get(a)
14
- const nb = nodeRegistry.get(b)
15
- if (!na || !nb) return
16
- const channel = type === 1 ? "exit" : "enter"
17
- na._emitCollision(channel, nb)
18
- nb._emitCollision(channel, na)
19
- })
20
- }