lecodes-cli 0.19.0 → 0.20.0

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 (270) hide show
  1. package/README.md +1 -0
  2. package/dist/index.js +1069 -396
  3. package/package.json +14 -13
  4. package/runtime/scene-harness.json +1 -1
  5. package/runtime/sdk-types.json +1 -1
  6. package/runtime/web/assets/Roboto-Variable-DHm9jpd-.woff2 +0 -0
  7. package/runtime/web/assets/__vite-browser-external-BIHI7g3E.js +1 -0
  8. package/runtime/web/assets/basis-C64VHDVD.js +1 -0
  9. package/runtime/web/assets/basis_transcoder-VXdx5NbI.wasm +0 -0
  10. package/runtime/web/assets/createViewerLite-BvHobVhp.js +852 -0
  11. package/runtime/web/assets/draco-BiISTFcR.js +118 -0
  12. package/runtime/web/assets/draco_decoder-DsQ12WqX.wasm +0 -0
  13. package/runtime/web/assets/index-CfNCCBjN.js +2 -0
  14. package/runtime/web/assets/mapViewImpl-B2JcES8l.js +810 -0
  15. package/runtime/web/assets/maplibre-gl-worker-CJfwIrte.js +8 -0
  16. package/runtime/web/assets/neutral_ibl128-D-lzdkLN.ktx +0 -0
  17. package/runtime/web/assets/worker-Caf-yYEI.js +2 -0
  18. package/runtime/web/embed.html +32 -0
  19. package/runtime/web/embed.js +171 -0
  20. package/src/api.ts +302 -0
  21. package/src/browserAuth.ts +87 -0
  22. package/src/cmgenTool.ts +104 -0
  23. package/src/commands/app.ts +896 -0
  24. package/src/commands/appAndroid.ts +603 -0
  25. package/src/commands/appDesktop.ts +339 -0
  26. package/src/commands/appDesktopMac.ts +470 -0
  27. package/src/commands/appIcon.ts +187 -0
  28. package/src/commands/appShared.ts +542 -0
  29. package/src/commands/appTemplates.ts +907 -0
  30. package/src/commands/appTemplatesAndroid.ts +599 -0
  31. package/src/commands/appTemplatesGradlew.ts +9 -0
  32. package/src/commands/assets.ts +28 -0
  33. package/src/commands/clone.ts +60 -0
  34. package/src/commands/compile.ts +253 -0
  35. package/src/commands/create.ts +59 -0
  36. package/src/commands/design.ts +863 -0
  37. package/src/commands/designTemplates.ts +11 -0
  38. package/src/commands/desktop.ts +127 -0
  39. package/src/commands/dev.ts +236 -0
  40. package/src/commands/diff.ts +59 -0
  41. package/src/commands/init.ts +191 -0
  42. package/src/commands/install.ts +198 -0
  43. package/src/commands/lightmap.ts +290 -0
  44. package/src/commands/link.ts +147 -0
  45. package/src/commands/login.ts +24 -0
  46. package/src/commands/navmesh.ts +225 -0
  47. package/src/commands/pn.ts +305 -0
  48. package/src/commands/projectTemplates.ts +130 -0
  49. package/src/commands/pull.ts +109 -0
  50. package/src/commands/push.ts +173 -0
  51. package/src/commands/render.ts +416 -0
  52. package/src/commands/scene.ts +148 -0
  53. package/src/commands/shaders.ts +157 -0
  54. package/src/commands/status.ts +29 -0
  55. package/src/commands/test.ts +371 -0
  56. package/src/commands/thumbs.ts +178 -0
  57. package/src/commands/types.ts +87 -0
  58. package/src/commands/update.ts +190 -0
  59. package/src/compile/assetIcons.ts +214 -0
  60. package/src/compile/collect.ts +46 -0
  61. package/src/compile/collectLocal.ts +37 -0
  62. package/src/compile/designCompile.ts +104 -0
  63. package/src/compile/fonts.ts +156 -0
  64. package/src/compile/headlessBundle.ts +129 -0
  65. package/src/compile/nativeStack.ts +21 -0
  66. package/src/compile/projectCompile.ts +109 -0
  67. package/src/compile/sceneCompile.ts +113 -0
  68. package/src/compile/screenEntry.ts +127 -0
  69. package/src/compile/shaders.ts +245 -0
  70. package/src/config.ts +42 -0
  71. package/src/designMeta.ts +35 -0
  72. package/src/desktopRenderer.ts +545 -0
  73. package/src/desktopScript.ts +276 -0
  74. package/src/dev/clientTemplates.ts +160 -0
  75. package/src/dev/devServer.ts +333 -0
  76. package/src/dev/webRunner.ts +22 -0
  77. package/src/dev/wsServer.ts +144 -0
  78. package/src/distRoot.ts +20 -0
  79. package/src/ignore.ts +163 -0
  80. package/src/index.ts +494 -0
  81. package/src/lecodes-3d-editor.d.ts +41 -0
  82. package/src/lecodes-assets.d.ts +7 -0
  83. package/src/lecodes-design.d.ts +191 -0
  84. package/src/lecodes-renderer.d.ts +131 -0
  85. package/src/localFiles.ts +144 -0
  86. package/src/manifest.ts +46 -0
  87. package/src/matcTool.ts +137 -0
  88. package/src/peers.ts +40 -0
  89. package/src/project.ts +61 -0
  90. package/src/projectEnv.ts +94 -0
  91. package/src/qrcode-terminal.d.ts +9 -0
  92. package/src/releases.ts +125 -0
  93. package/src/serverDiff.ts +78 -0
  94. package/src/textDiff.ts +103 -0
  95. package/src/types.ts +0 -0
  96. package/src/util.ts +146 -0
  97. package/runtime/sdk/animate/animate.ts +0 -238
  98. package/runtime/sdk/animate/bezier.ts +0 -138
  99. package/runtime/sdk/animate/easings.ts +0 -126
  100. package/runtime/sdk/canvas/Canvas.ts +0 -305
  101. package/runtime/sdk/compile/aspectMacro.ts +0 -86
  102. package/runtime/sdk/compile/assetIconMacro.ts +0 -384
  103. package/runtime/sdk/compile/assetMacro.ts +0 -146
  104. package/runtime/sdk/compile/assetName.ts +0 -50
  105. package/runtime/sdk/compile/bundler.ts +0 -287
  106. package/runtime/sdk/compile/compileProject.ts +0 -129
  107. package/runtime/sdk/compile/detectEntry.ts +0 -128
  108. package/runtime/sdk/compile/fontMacro.ts +0 -459
  109. package/runtime/sdk/compile/fontRegistry.ts +0 -78
  110. package/runtime/sdk/compile/header.ts +0 -67
  111. package/runtime/sdk/compile/index.ts +0 -100
  112. package/runtime/sdk/compile/libraryImports.ts +0 -52
  113. package/runtime/sdk/compile/liteMaterial.ts +0 -247
  114. package/runtime/sdk/compile/sceneEditor.ts +0 -88
  115. package/runtime/sdk/compile/serverSplit.ts +0 -233
  116. package/runtime/sdk/compile/serverTypes.ts +0 -227
  117. package/runtime/sdk/compile/sfnt.ts +0 -98
  118. package/runtime/sdk/compile/shaderSchema.ts +0 -202
  119. package/runtime/sdk/compile/shaderTargets.ts +0 -81
  120. package/runtime/sdk/compile/sourcemap.ts +0 -25
  121. package/runtime/sdk/core/Aspect.ts +0 -568
  122. package/runtime/sdk/core/InspectorUI.ts +0 -212
  123. package/runtime/sdk/core/StateMachine.ts +0 -308
  124. package/runtime/sdk/core/color.ts +0 -66
  125. package/runtime/sdk/core/compWrite.ts +0 -42
  126. package/runtime/sdk/core/events.ts +0 -38
  127. package/runtime/sdk/core/fields.ts +0 -120
  128. package/runtime/sdk/core/registry.ts +0 -23
  129. package/runtime/sdk/core/signals.ts +0 -277
  130. package/runtime/sdk/core/time.ts +0 -84
  131. package/runtime/sdk/g2/Camera2D.ts +0 -40
  132. package/runtime/sdk/g2/CharacterController2D.ts +0 -276
  133. package/runtime/sdk/g2/Node2D.ts +0 -267
  134. package/runtime/sdk/g2/OneWay2D.ts +0 -66
  135. package/runtime/sdk/g2/Physics2D.ts +0 -346
  136. package/runtime/sdk/g2/Scene2D.ts +0 -209
  137. package/runtime/sdk/g2/Shape2D.ts +0 -259
  138. package/runtime/sdk/g2/Sprite.ts +0 -89
  139. package/runtime/sdk/g2/SpriteAnimation.ts +0 -171
  140. package/runtime/sdk/g2/SpriteSheet.ts +0 -166
  141. package/runtime/sdk/g2/Texture2D.ts +0 -47
  142. package/runtime/sdk/g2/Tilemap.ts +0 -41
  143. package/runtime/sdk/g2/Tileset.ts +0 -71
  144. package/runtime/sdk/g2/Trigger2D.ts +0 -77
  145. package/runtime/sdk/g2/autotile.ts +0 -433
  146. package/runtime/sdk/g2/cells.ts +0 -91
  147. package/runtime/sdk/g2/defineScene2d.ts +0 -381
  148. package/runtime/sdk/g2/groups2d.ts +0 -106
  149. package/runtime/sdk/g2/loop.ts +0 -50
  150. package/runtime/sdk/g2/scenarios2d.ts +0 -69
  151. package/runtime/sdk/g2/touch.ts +0 -83
  152. package/runtime/sdk/gl/Camera.ts +0 -160
  153. package/runtime/sdk/gl/CameraPlace.ts +0 -52
  154. package/runtime/sdk/gl/CharacterController.ts +0 -238
  155. package/runtime/sdk/gl/Gearbox.ts +0 -212
  156. package/runtime/sdk/gl/Geometry.ts +0 -279
  157. package/runtime/sdk/gl/IK.ts +0 -193
  158. package/runtime/sdk/gl/InstancedMesh.ts +0 -132
  159. package/runtime/sdk/gl/Light.ts +0 -107
  160. package/runtime/sdk/gl/Lightmap.ts +0 -256
  161. package/runtime/sdk/gl/Material.ts +0 -260
  162. package/runtime/sdk/gl/Mesh.ts +0 -83
  163. package/runtime/sdk/gl/Model.ts +0 -124
  164. package/runtime/sdk/gl/NavAgent.ts +0 -337
  165. package/runtime/sdk/gl/NavMesh.ts +0 -397
  166. package/runtime/sdk/gl/Node.ts +0 -350
  167. package/runtime/sdk/gl/Noise.ts +0 -30
  168. package/runtime/sdk/gl/Particles.ts +0 -676
  169. package/runtime/sdk/gl/Physics.ts +0 -222
  170. package/runtime/sdk/gl/Plane.ts +0 -53
  171. package/runtime/sdk/gl/Ragdoll.ts +0 -270
  172. package/runtime/sdk/gl/Ray.ts +0 -16
  173. package/runtime/sdk/gl/Scene.ts +0 -486
  174. package/runtime/sdk/gl/Shape.ts +0 -409
  175. package/runtime/sdk/gl/Terrain.ts +0 -1092
  176. package/runtime/sdk/gl/Texture.ts +0 -63
  177. package/runtime/sdk/gl/Trigger.ts +0 -45
  178. package/runtime/sdk/gl/Vehicle.ts +0 -473
  179. package/runtime/sdk/gl/Wheel.ts +0 -240
  180. package/runtime/sdk/gl/animation/AnimationClip.ts +0 -204
  181. package/runtime/sdk/gl/animation/Animator.ts +0 -120
  182. package/runtime/sdk/gl/animation/Layer.ts +0 -31
  183. package/runtime/sdk/gl/animation/Loop.ts +0 -30
  184. package/runtime/sdk/gl/animation/Playback.ts +0 -44
  185. package/runtime/sdk/gl/animation/core.ts +0 -336
  186. package/runtime/sdk/gl/controls.ts +0 -95
  187. package/runtime/sdk/gl/physicsEvents.ts +0 -20
  188. package/runtime/sdk/gl/scenarios.ts +0 -291
  189. package/runtime/sdk/gl/state.ts +0 -6
  190. package/runtime/sdk/gl/terrainMesh.ts +0 -219
  191. package/runtime/sdk/gl/touch.ts +0 -68
  192. package/runtime/sdk/inject.ts +0 -197
  193. package/runtime/sdk/math/Mathf.ts +0 -118
  194. package/runtime/sdk/math/mat4.ts +0 -278
  195. package/runtime/sdk/math/quat.ts +0 -232
  196. package/runtime/sdk/math/vec.ts +0 -255
  197. package/runtime/sdk/net/codec.ts +0 -119
  198. package/runtime/sdk/net/core.ts +0 -384
  199. package/runtime/sdk/net/index.ts +0 -181
  200. package/runtime/sdk/net/replication.ts +0 -622
  201. package/runtime/sdk/plugins/camera.ts +0 -81
  202. package/runtime/sdk/plugins/geolocation.ts +0 -123
  203. package/runtime/sdk/plugins/oauth.ts +0 -61
  204. package/runtime/sdk/plugins/permission.ts +0 -7
  205. package/runtime/sdk/plugins/push.ts +0 -132
  206. package/runtime/sdk/plugins/qr.ts +0 -73
  207. package/runtime/sdk/plugins/service.ts +0 -47
  208. package/runtime/sdk/runtime/app.ts +0 -101
  209. package/runtime/sdk/runtime/appEvents.ts +0 -54
  210. package/runtime/sdk/runtime/channel.ts +0 -61
  211. package/runtime/sdk/runtime/clipboard.ts +0 -20
  212. package/runtime/sdk/runtime/datetime.ts +0 -329
  213. package/runtime/sdk/runtime/device.ts +0 -293
  214. package/runtime/sdk/runtime/fetch.ts +0 -77
  215. package/runtime/sdk/runtime/files.ts +0 -23
  216. package/runtime/sdk/runtime/input.ts +0 -175
  217. package/runtime/sdk/runtime/media.ts +0 -111
  218. package/runtime/sdk/runtime/misc.ts +0 -16
  219. package/runtime/sdk/runtime/net.ts +0 -36
  220. package/runtime/sdk/runtime/rpc.ts +0 -218
  221. package/runtime/sdk/runtime/service.ts +0 -83
  222. package/runtime/sdk/runtime/share.ts +0 -9
  223. package/runtime/sdk/runtime/storage.ts +0 -13
  224. package/runtime/sdk/runtime/touch.ts +0 -76
  225. package/runtime/sdk/scene/defineScene.ts +0 -1353
  226. package/runtime/sdk/scene/editorPlugins.ts +0 -110
  227. package/runtime/sdk/scene/gizmos.ts +0 -148
  228. package/runtime/sdk/scene/grammar.ts +0 -120
  229. package/runtime/sdk/scene/material.ts +0 -188
  230. package/runtime/sdk/server/auth/appConfig.ts +0 -12
  231. package/runtime/sdk/server/auth/global.ts +0 -80
  232. package/runtime/sdk/server/auth/host.ts +0 -318
  233. package/runtime/sdk/server/auth/models.ts +0 -83
  234. package/runtime/sdk/server/auth/types.ts +0 -50
  235. package/runtime/sdk/server/channel.ts +0 -56
  236. package/runtime/sdk/server/context.ts +0 -36
  237. package/runtime/sdk/server/db/defineDb.ts +0 -237
  238. package/runtime/sdk/server/db/fields.ts +0 -132
  239. package/runtime/sdk/server/db/httpTransport.ts +0 -93
  240. package/runtime/sdk/server/db/index.ts +0 -7
  241. package/runtime/sdk/server/db/marci/query.ts +0 -412
  242. package/runtime/sdk/server/db/types.ts +0 -202
  243. package/runtime/sdk/server/errors.ts +0 -12
  244. package/runtime/sdk/server/host.ts +0 -74
  245. package/runtime/sdk/server/inject.ts +0 -13
  246. package/runtime/sdk/server/runtime.ts +0 -133
  247. package/runtime/sdk/server/validate.ts +0 -87
  248. package/runtime/sdk/ui/NativeView.ts +0 -142
  249. package/runtime/sdk/ui/UI.ts +0 -39
  250. package/runtime/sdk/ui/UIBottomSheet.ts +0 -139
  251. package/runtime/sdk/ui/UIButton.ts +0 -101
  252. package/runtime/sdk/ui/UIContainer.ts +0 -60
  253. package/runtime/sdk/ui/UIImage.ts +0 -83
  254. package/runtime/sdk/ui/UIInput.ts +0 -185
  255. package/runtime/sdk/ui/UIModal.ts +0 -139
  256. package/runtime/sdk/ui/UINode.ts +0 -830
  257. package/runtime/sdk/ui/UIPager.ts +0 -362
  258. package/runtime/sdk/ui/UIPopover.ts +0 -100
  259. package/runtime/sdk/ui/UIScreen.ts +0 -123
  260. package/runtime/sdk/ui/UIScrollable.ts +0 -87
  261. package/runtime/sdk/ui/UISpacer.ts +0 -14
  262. package/runtime/sdk/ui/UITabs.ts +0 -236
  263. package/runtime/sdk/ui/UIText.ts +0 -51
  264. package/runtime/sdk/ui/UIVideo.ts +0 -88
  265. package/runtime/sdk/ui/UIVirtualizedList.ts +0 -241
  266. package/runtime/sdk/ui/UIWidget.ts +0 -127
  267. package/runtime/sdk/ui/fonts.ts +0 -13
  268. package/runtime/sdk/ui/presentable.ts +0 -117
  269. package/runtime/sdk/ui/router.ts +0 -132
  270. package/runtime/sdk/ui/theme.ts +0 -84
@@ -1,44 +0,0 @@
1
- // Playback — the handle `play()` returns (docs/animator-plan.md). Thenable: `await anim.play('slash')`
2
- // resolves at the clip's HAND-OVER — where its fade-back starts (`end − fadeOut`; the end when there
3
- // is no fadeOut) — with `true`; `false` when it was cut short (replaced / stopped). What
4
- // you call right after the await is what the clip fades into: `anim.play(next)` (a chained one-shot,
5
- // crossfaded), `anim.playLoop(next)` — nothing = back to the loop (or hold, on a layer without one).
6
- // Returning one from an `async` function flattens it into `Promise<boolean>` — return `p.done` (or
7
- // make the function sync) when the caller needs the handle.
8
-
9
- import type { AnimationClip } from "./AnimationClip"
10
- import type { Core, PlaybackRec, StopOptions } from "./core"
11
- import type { Layer } from "./Layer"
12
-
13
- export class Playback implements PromiseLike<boolean> {
14
- private readonly _r: PlaybackRec
15
- private readonly _c: Core
16
- /** @internal — created by play(). */
17
- constructor(rec: PlaybackRec, core: Core) { this._r = rec; this._c = core }
18
-
19
- /** The clip that plays. */
20
- get clip(): AnimationClip { return this._r.slot.clip }
21
- /** The layer it plays on. */
22
- get layer(): Layer { return this._r.slot.layer.view }
23
- /** Clock (s). */
24
- get time(): number { return this._r.slot.last === this._r ? this._c.slotTime(this._r.slot) : 0 }
25
- /** 0–1 through the clip (the current cycle, when looping). */
26
- get progress(): number { const d = this.clip.duration; return d > 0 ? this.time / d : 0 }
27
- /** Not handed over yet (the clip still owns the layer). */
28
- get playing(): boolean { return this._r.playing }
29
- /** Current contribution to the pose, 0–1 — moves during crossfades, still falling after the
30
- * hand-over while the clip fades beneath what took over. */
31
- get weight(): number { return this._r.slot.last === this._r ? this._c.slotWeight(this._r.slot) : 0 }
32
- /** Resolves at the hand-over (`true`) or when cut short (`false`) — the promise `await playback`
33
- * uses; the one to return from async functions or put in `Promise.all`. */
34
- get done(): Promise<boolean> { return this._r.done }
35
-
36
- then<A = boolean, B = never>(onFulfilled?: ((v: boolean) => A | PromiseLike<A>) | null, onRejected?: ((e: unknown) => B | PromiseLike<B>) | null): Promise<A | B> {
37
- return this._r.done.then(onFulfilled, onRejected)
38
- }
39
-
40
- /** Move the clock (s). */
41
- seek(time: number): this { this._c.seek(this._r.slot, time); return this }
42
- /** Cut this clip short: fade it out (the layer's loop returns if nothing else is coming in). */
43
- stop(options: StopOptions = {}): this { this._c.stopSlot(this._r.slot, options.fade ?? 0); return this }
44
- }
@@ -1,336 +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
- /** Where in the cycle to start, 0–1 (default 0): a blend's members share one clock, so this is the
40
- * phase of every member — e.g. the phase whose pose matches the clip the loop takes over from. */
41
- phase?: number
42
- }
43
- /** What a layer loops: one clip (name / index), or a blend space `{ name: position }`. */
44
- export type LoopDef = string | number | Record<string, BlendPosition>
45
-
46
- /** One clip the layer shows this frame — `layer.active` / `anim.active` (a debug overlay's list). */
47
- export type ActiveClip = {
48
- name: string
49
- clip: AnimationClip
50
- /** contribution to the pose, 0–1 — the crossfades' state */
51
- weight: number
52
- /** clock (s) */
53
- time: number
54
- /** a member of the layer's loop (else a one-shot — playing, or fading after its hand-over) */
55
- loop: boolean
56
- }
57
-
58
- export type LayerOptions = {
59
- /** Bone name(s): the layer drives only these subtrees (`'Spine1'` = upper body). Default: the whole rig. */
60
- mask?: string | string[]
61
- /** Blend each clip's DELTA from its own first frame on top of the layers below (lean, breathe, recoil)
62
- * instead of overriding them. */
63
- additive?: boolean
64
- /** Layer contribution 0–1 (default 1). */
65
- weight?: number
66
- }
67
-
68
- export type ClipEventHandler = (clip: string, layer: Layer) => void
69
-
70
- // ---- records -------------------------------------------------------------------------------------
71
-
72
- /** One INSTANCE of a clip on a layer (an engine slot). A clip has as many as it needs at once: a
73
- * clip started again while its last instance still fades gets another slot — the old one keeps
74
- * fading beneath (a rewind in place would snap the pose). `playback` = the one-shot that owns it
75
- * until its hand-over; `last` = the one that took it most recently (its weight / time after that). */
76
- export type SlotRec = { name: string, clip: AnimationClip, slot: number, layer: LayerRec, inBlend: boolean, playback?: PlaybackRec, last?: PlaybackRec }
77
- export type PlaybackRec = { slot: SlotRec, playing: boolean, done: Promise<boolean>, resolve: (natural: boolean) => void }
78
- export type LayerRec = { index: number, view: Layer, slots: SlotRec[], loop?: LoopRec, weight: number, mask: string, additive: boolean }
79
- export type LoopRec = { layer: LayerRec, members: { name: string, slot: SlotRec }[], value: BlendPosition, view: Loop, dead: boolean }
80
-
81
- const handlers = new Map<number, (slot: number, type: number) => void>()
82
- let eventsInstalled = false
83
- const ensureEvents = (): void => {
84
- if (eventsInstalled) return
85
- eventsInstalled = true
86
- _creator.setOnAnimatorEvent((animatorId, slot, type) => handlers.get(animatorId)?.(slot, type))
87
- }
88
-
89
- const settled = (slot: SlotRec): PlaybackRec => {
90
- let resolve = (_n: boolean): void => {}
91
- const done = new Promise<boolean>((r) => { resolve = r })
92
- resolve(false)
93
- return { slot, playing: false, done, resolve }
94
- }
95
-
96
- export class Core {
97
- id = 0
98
- /** Added clips by name (`addClip`) — the lookup index; an added name overrides an embedded one. */
99
- readonly table = new Map<string, AnimationClip>()
100
- readonly layers: LayerRec[] = []
101
- speed = 1
102
- private _embedded?: AnimationClip[]
103
- private readonly _added: { name: string, clip: AnimationClip }[] = []
104
- private _list?: AnimationClip[]
105
- private readonly _listeners = new Map<string, Set<ClipEventHandler>>()
106
-
107
- readonly node: Node
108
- constructor(node: Node) { this.node = node; this.layers.push(this.newLayer()) }
109
-
110
- // ---- clips ----
111
- embedded(): AnimationClip[] {
112
- if (!this._embedded) {
113
- this._embedded = AnimationClip._ofSet(_creator.getGlbClipSet(this.node.id))
114
- const seen = new Set<string>()
115
- for (const c of this._embedded) {
116
- 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))`)
117
- seen.add(c.name)
118
- }
119
- }
120
- return this._embedded
121
- }
122
- /** The ordered clip list: the GLB's clips in file order (an added clip of the same name takes the
123
- * embedded one's place), then the remaining added clips in insertion order. Cached until addClip. */
124
- clips(): AnimationClip[] {
125
- if (this._list) return this._list
126
- const out: AnimationClip[] = []
127
- const placed = new Set<string>()
128
- for (const c of this.embedded()) {
129
- const override = this.table.get(c.name)
130
- if (override && !placed.has(c.name)) { out.push(override); placed.add(c.name) }
131
- else out.push(c)
132
- }
133
- for (const a of this._added) if (!placed.has(a.name)) { out.push(a.clip); placed.add(a.name) }
134
- return this._list = out
135
- }
136
- addClip(name: string, clip: AnimationClip): void {
137
- const i = this._added.findIndex((a) => a.name === name)
138
- if (i >= 0) this._added[i] = { name, clip }
139
- else this._added.push({ name, clip })
140
- this.table.set(name, clip)
141
- this._list = undefined
142
- }
143
- /** A clip by name (added first, then embedded), or by index into `clips()` (none = the first). */
144
- resolveClip(ref: string | number | undefined): [string, AnimationClip] | undefined {
145
- if (ref === undefined || typeof ref === "number") { const c = this.clips()[ref ?? 0]; return c ? [ c.name, c ] : undefined }
146
- const c = this.table.get(ref) ?? this.embedded().find((e) => e.name === ref)
147
- return c ? [ ref, c ] : undefined
148
- }
149
- private clipNames(): string { return this.clips().map((c) => c.name).join(", ") || "(empty)" }
150
-
151
- // ---- lifecycle ----
152
- /** Create the native animator on demand. False = nothing to animate (warned). */
153
- ensure(): boolean {
154
- if (this.id) return true
155
- 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 }
156
- ensureEvents()
157
- this.id = _creator.animatorCreate(this.node.id)
158
- if (!this.id) { console.warn("Animator: node has no transform hierarchy"); return false }
159
- handlers.set(this.id, (slot, type) => this.onEvent(slot, type))
160
- if (this.speed !== 1) _creator.animatorSetGlobal(this.id, this.speed, false)
161
- for (const L of this.layers) if (L.index > 0) this.pushLayer(L)
162
- return true
163
- }
164
- destroy(): void {
165
- for (const L of this.layers) for (const s of L.slots) if (s.playback) this.settle(s.playback, false)
166
- if (this.id) { _creator.animatorDestroy(this.id); handlers.delete(this.id); this.id = 0 }
167
- for (const L of this.layers) { L.slots = []; if (L.loop) { L.loop.dead = true; L.loop = undefined } }
168
- }
169
-
170
- // ---- layers ----
171
- newLayer(options?: LayerOptions): LayerRec {
172
- const rec: LayerRec = { index: this.layers.length, view: undefined as unknown as Layer, slots: [], weight: options?.weight ?? 1, mask: "", additive: options?.additive ?? false }
173
- if (options?.mask !== undefined) rec.mask = Array.isArray(options.mask) ? options.mask.join("\n") : options.mask
174
- rec.view = new Layer(rec, this)
175
- return rec
176
- }
177
- addLayer(options: LayerOptions): Layer {
178
- if (this.layers.length >= 255) { console.warn("Animator: too many layers"); return this.layers[this.layers.length - 1].view }
179
- const rec = this.newLayer(options)
180
- this.layers.push(rec)
181
- if (this.id) this.pushLayer(rec)
182
- return rec.view
183
- }
184
- pushLayer(L: LayerRec): void {
185
- if (!this.id) return
186
- 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"}`)
187
- _creator.animatorSetLayer(this.id, L.index, L.weight, L.additive, L.mask)
188
- }
189
-
190
- // ---- slots ----
191
- /** A slot for the clip on the layer: one of its own that is silent now (never shown, or faded
192
- * out), else a new binding — so a clip still fading can start again OVER itself. */
193
- slotFor(L: LayerRec, name: string, clip: AnimationClip): SlotRec | undefined {
194
- const have = L.slots.find((s) => s.name === name && s.clip === clip && !s.inBlend && !s.playback && this.slotWeight(s) <= 0)
195
- if (have) return have
196
- const slot = _creator.animatorBind(this.id, clip._set, clip._index, L.index)
197
- if (slot < 0) { console.warn(`Animator: could not bind clip '${name}'`); return undefined }
198
- const bound = _creator.animatorBoundTracks(this.id, slot)
199
- if (clip.trackCount > 0 && bound === 0) console.warn(`Animator: '${name}' — no track matched a node name under ${this.node.name || "the node"} (different rig?)`)
200
- else if (bound < clip.trackCount) console.warn(`Animator: '${name}' — ${clip.trackCount - bound}/${clip.trackCount} tracks unbound (bone names not found)`)
201
- const rec: SlotRec = { name, clip, slot, layer: L, inBlend: false }
202
- L.slots.push(rec)
203
- return rec
204
- }
205
-
206
- // ---- play / stop ----
207
- play(L: LayerRec, ref: string | number | undefined, o: PlayOptions): Playback {
208
- const found = this.resolveClip(ref)
209
- if (!found) {
210
- console.warn(`Animator: no clip ${ref === undefined ? "to play" : JSON.stringify(ref)} on ${this.node.name || "the node"} — table: ${this.clipNames()}`)
211
- return new Playback(settled({ name: "", clip: AnimationClip._none(), slot: -1, layer: L, inBlend: false }), this)
212
- }
213
- const [name, clip] = found
214
- if (!this.ensure()) return new Playback(settled({ name, clip, slot: -1, layer: L, inBlend: false }), this)
215
- const s = this.slotFor(L, name, clip)
216
- if (!s) return new Playback(settled({ name, clip, slot: -1, layer: L, inBlend: false }), this)
217
- 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) }
218
- const fadeIn = o.fadeIn ?? o.fade ?? 0
219
- const fadeOut = o.fadeOut ?? o.fade ?? -1
220
- // the other one-shots on this layer are cut short (their promises resolve false)
221
- for (const other of L.slots) if (other.playback) this.settle(other.playback, false)
222
- let resolve = (_n: boolean): void => {}
223
- const done = new Promise<boolean>((r) => { resolve = r })
224
- const rec: PlaybackRec = { slot: s, playing: true, done, resolve }
225
- s.playback = rec; s.last = rec
226
- _creator.animatorPlay(this.id, s.slot, false, o.speed ?? 1, fadeIn, fadeOut, o.restart ?? false)
227
- return new Playback(rec, this)
228
- }
229
- /** A one-shot on the layer hasn't handed over yet. */
230
- busy(L: LayerRec): boolean { return L.slots.some((s) => s.playback?.playing) }
231
- settle(p: PlaybackRec, natural: boolean): void {
232
- p.playing = false
233
- if (p.slot.playback === p) p.slot.playback = undefined
234
- p.resolve(natural)
235
- }
236
- stopSlot(s: SlotRec, fade: number): void {
237
- if (s.playback) this.settle(s.playback, false)
238
- if (this.id && s.slot >= 0) _creator.animatorStop(this.id, s.layer.index, s.slot, fade)
239
- }
240
- stopLayer(L: LayerRec, fade: number): void {
241
- for (const s of L.slots) if (s.playback) this.settle(s.playback, false)
242
- if (L.loop) { L.loop.dead = true; L.loop = undefined }
243
- if (this.id) _creator.animatorStop(this.id, L.index, -1, fade)
244
- }
245
- stopAll(fade: number): void { for (const L of this.layers) this.stopLayer(L, fade) }
246
-
247
- // ---- loop ----
248
- loop(L: LayerRec, def: LoopDef, o: LoopOptions): Loop | undefined {
249
- if (!this.ensure()) return undefined
250
- const fade = o.fade ?? 0
251
- if (L.loop) { L.loop.dead = true; for (const m of L.loop.members) m.slot.inBlend = false; L.loop = undefined }
252
- for (const s of L.slots) if (s.playback) this.settle(s.playback, false) // one-shots on the layer are cut short
253
- const space: Record<string, BlendPosition> = typeof def === "object" ? def : {}
254
- if (typeof def !== "object") {
255
- const found = this.resolveClip(def)
256
- if (!found) { console.warn(`Animator: no clip ${JSON.stringify(def)} to loop — table: ${this.clipNames()}`); return undefined }
257
- space[found[0]] = 0
258
- }
259
- const rec: LoopRec = { layer: L, members: [], value: 0, view: undefined as unknown as Loop, dead: false }
260
- rec.view = new Loop(rec, this)
261
- const slots: number[] = [], positions: number[] = []
262
- const is2D = Object.values(space).some((p) => typeof p !== "number")
263
- for (const [name, at] of Object.entries(space)) {
264
- const found = this.resolveClip(name)
265
- if (!found) { console.warn(`Animator: loop clip '${name}' is not in the clip table`); continue }
266
- const s = this.slotFor(L, name, found[1])
267
- if (!s) continue
268
- if (s.playback) this.settle(s.playback, false)
269
- s.inBlend = true
270
- rec.members.push({ name, slot: s })
271
- slots.push(s.slot)
272
- if (is2D) positions.push(typeof at === "number" ? at : at[0], typeof at === "number" ? 0 : at[1])
273
- else positions.push(at as number)
274
- }
275
- L.loop = rec
276
- _creator.animatorSetBlend(this.id, L.index, is2D ? 2 : 1, new Uint16Array(slots), new Float32Array(positions), fade, o.speed ?? 1)
277
- if (o.phase !== undefined) this.setLoopPhase(rec, o.phase)
278
- return rec.view
279
- }
280
- /** The loop's phase through its cycle, 0–1 (the members share one clock). */
281
- loopPhase(b: LoopRec): number {
282
- const m = b.members[0]
283
- if (!m || b.dead) return 0
284
- const d = m.slot.clip.duration
285
- return d > 0 ? this.slotTime(m.slot) / d : 0
286
- }
287
- setLoopPhase(b: LoopRec, phase: number): void {
288
- const m = b.members[0]
289
- if (!m || b.dead) return
290
- this.seek(m.slot, (((phase % 1) + 1) % 1) * m.slot.clip.duration) // seeking one member moves the group
291
- }
292
- setLoopValue(b: LoopRec, v: BlendPosition): void {
293
- if (b.dead) { console.warn("Animator: this loop was replaced — use the object the latest playLoop() returned"); return }
294
- b.value = v
295
- if (!this.id) return
296
- const [x, y] = typeof v === "number" ? [ v, 0 ] : [ v[0], v[1] ]
297
- _creator.animatorSetBlendValue(this.id, b.layer.index, x, y)
298
- }
299
- /** What the layer shows this frame: every instance with a weight, in binding order (stable rows). */
300
- active(L: LayerRec): ActiveClip[] {
301
- const out: ActiveClip[] = []
302
- for (const s of L.slots) {
303
- const weight = this.slotWeight(s)
304
- if (weight <= 0) continue
305
- out.push({ name: s.name, clip: s.clip, weight, time: this.slotTime(s), loop: s.inBlend })
306
- }
307
- return out
308
- }
309
- slotWeight(s: SlotRec): number { return this.id && s.slot >= 0 ? _creator.animatorGetSlotWeight(this.id, s.slot) : 0 }
310
- slotTime(s: SlotRec): number { return this.id && s.slot >= 0 ? _creator.animatorGetSlotTime(this.id, s.slot) : 0 }
311
- seek(s: SlotRec, time: number): void { if (this.id && s.slot >= 0) _creator.animatorSeek(this.id, s.slot, time) }
312
-
313
- // ---- props ----
314
- setSpeed(v: number): void { this.speed = v; if (this.id) _creator.animatorSetGlobal(this.id, v, false) }
315
- setRootMotion(on: boolean, rotation = false): void { if (this.ensure()) _creator.animatorSetRootMotion(this.id, on ? "*" : "", (on ? 2 : 0) | (on && rotation ? 4 : 0)) }
316
-
317
- // ---- events ----
318
- on(event: string, cb: ClipEventHandler): void { (this._listeners.get(event) ?? this._listeners.set(event, new Set()).get(event)!).add(cb) }
319
- off(event: string, cb: ClipEventHandler): void { this._listeners.get(event)?.delete(cb) }
320
- /** Native slot event: 0 completed / 1 loop / 2 settled (faded to 0) / 3 hand-over / 4+i clip event i. */
321
- private onEvent(slotIndex: number, type: number): void {
322
- let s: SlotRec | undefined
323
- for (const L of this.layers) { s = L.slots.find((x) => x.slot === slotIndex); if (s) break }
324
- if (!s) return
325
- if (type >= 4) {
326
- const name = s.clip._events[type - 4]?.name
327
- const cbs = name ? this._listeners.get(name) : undefined
328
- if (cbs) for (const cb of [ ...cbs ]) cb(s.name, s.layer.view)
329
- return
330
- }
331
- const p = s.playback
332
- if (!p) return
333
- if (type === 3) this.settle(p, true) // hand-over: the clip is over; the app's next call redirects the fade
334
- else if (type === 2) this.settle(p, false) // faded out after a stop / replacement before handing over
335
- }
336
- }
@@ -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
- }