lecodes-cli 0.19.0 → 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 (254) hide show
  1. package/dist/index.js +730 -235
  2. package/package.json +14 -13
  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/compile/aspectMacro.ts +0 -86
  86. package/runtime/sdk/compile/assetIconMacro.ts +0 -384
  87. package/runtime/sdk/compile/assetMacro.ts +0 -146
  88. package/runtime/sdk/compile/assetName.ts +0 -50
  89. package/runtime/sdk/compile/bundler.ts +0 -287
  90. package/runtime/sdk/compile/compileProject.ts +0 -129
  91. package/runtime/sdk/compile/detectEntry.ts +0 -128
  92. package/runtime/sdk/compile/fontMacro.ts +0 -459
  93. package/runtime/sdk/compile/fontRegistry.ts +0 -78
  94. package/runtime/sdk/compile/header.ts +0 -67
  95. package/runtime/sdk/compile/index.ts +0 -100
  96. package/runtime/sdk/compile/libraryImports.ts +0 -52
  97. package/runtime/sdk/compile/liteMaterial.ts +0 -247
  98. package/runtime/sdk/compile/sceneEditor.ts +0 -88
  99. package/runtime/sdk/compile/serverSplit.ts +0 -233
  100. package/runtime/sdk/compile/serverTypes.ts +0 -227
  101. package/runtime/sdk/compile/sfnt.ts +0 -98
  102. package/runtime/sdk/compile/shaderSchema.ts +0 -202
  103. package/runtime/sdk/compile/shaderTargets.ts +0 -81
  104. package/runtime/sdk/compile/sourcemap.ts +0 -25
  105. package/runtime/sdk/core/Aspect.ts +0 -568
  106. package/runtime/sdk/core/InspectorUI.ts +0 -212
  107. package/runtime/sdk/core/StateMachine.ts +0 -308
  108. package/runtime/sdk/core/color.ts +0 -66
  109. package/runtime/sdk/core/compWrite.ts +0 -42
  110. package/runtime/sdk/core/events.ts +0 -38
  111. package/runtime/sdk/core/fields.ts +0 -120
  112. package/runtime/sdk/core/registry.ts +0 -23
  113. package/runtime/sdk/core/signals.ts +0 -277
  114. package/runtime/sdk/core/time.ts +0 -84
  115. package/runtime/sdk/g2/Camera2D.ts +0 -40
  116. package/runtime/sdk/g2/CharacterController2D.ts +0 -276
  117. package/runtime/sdk/g2/Node2D.ts +0 -267
  118. package/runtime/sdk/g2/OneWay2D.ts +0 -66
  119. package/runtime/sdk/g2/Physics2D.ts +0 -346
  120. package/runtime/sdk/g2/Scene2D.ts +0 -209
  121. package/runtime/sdk/g2/Shape2D.ts +0 -259
  122. package/runtime/sdk/g2/Sprite.ts +0 -89
  123. package/runtime/sdk/g2/SpriteAnimation.ts +0 -171
  124. package/runtime/sdk/g2/SpriteSheet.ts +0 -166
  125. package/runtime/sdk/g2/Texture2D.ts +0 -47
  126. package/runtime/sdk/g2/Tilemap.ts +0 -41
  127. package/runtime/sdk/g2/Tileset.ts +0 -71
  128. package/runtime/sdk/g2/Trigger2D.ts +0 -77
  129. package/runtime/sdk/g2/autotile.ts +0 -433
  130. package/runtime/sdk/g2/cells.ts +0 -91
  131. package/runtime/sdk/g2/defineScene2d.ts +0 -381
  132. package/runtime/sdk/g2/groups2d.ts +0 -106
  133. package/runtime/sdk/g2/loop.ts +0 -50
  134. package/runtime/sdk/g2/scenarios2d.ts +0 -69
  135. package/runtime/sdk/g2/touch.ts +0 -83
  136. package/runtime/sdk/gl/Camera.ts +0 -160
  137. package/runtime/sdk/gl/CameraPlace.ts +0 -52
  138. package/runtime/sdk/gl/CharacterController.ts +0 -238
  139. package/runtime/sdk/gl/Gearbox.ts +0 -212
  140. package/runtime/sdk/gl/Geometry.ts +0 -279
  141. package/runtime/sdk/gl/IK.ts +0 -193
  142. package/runtime/sdk/gl/InstancedMesh.ts +0 -132
  143. package/runtime/sdk/gl/Light.ts +0 -107
  144. package/runtime/sdk/gl/Lightmap.ts +0 -256
  145. package/runtime/sdk/gl/Material.ts +0 -260
  146. package/runtime/sdk/gl/Mesh.ts +0 -83
  147. package/runtime/sdk/gl/Model.ts +0 -124
  148. package/runtime/sdk/gl/NavAgent.ts +0 -337
  149. package/runtime/sdk/gl/NavMesh.ts +0 -397
  150. package/runtime/sdk/gl/Node.ts +0 -350
  151. package/runtime/sdk/gl/Noise.ts +0 -30
  152. package/runtime/sdk/gl/Particles.ts +0 -676
  153. package/runtime/sdk/gl/Physics.ts +0 -222
  154. package/runtime/sdk/gl/Plane.ts +0 -53
  155. package/runtime/sdk/gl/Ragdoll.ts +0 -270
  156. package/runtime/sdk/gl/Ray.ts +0 -16
  157. package/runtime/sdk/gl/Scene.ts +0 -486
  158. package/runtime/sdk/gl/Shape.ts +0 -409
  159. package/runtime/sdk/gl/Terrain.ts +0 -1092
  160. package/runtime/sdk/gl/Texture.ts +0 -63
  161. package/runtime/sdk/gl/Trigger.ts +0 -45
  162. package/runtime/sdk/gl/Vehicle.ts +0 -473
  163. package/runtime/sdk/gl/Wheel.ts +0 -240
  164. package/runtime/sdk/gl/animation/AnimationClip.ts +0 -204
  165. package/runtime/sdk/gl/animation/Animator.ts +0 -120
  166. package/runtime/sdk/gl/animation/Layer.ts +0 -31
  167. package/runtime/sdk/gl/animation/Loop.ts +0 -30
  168. package/runtime/sdk/gl/animation/Playback.ts +0 -44
  169. package/runtime/sdk/gl/animation/core.ts +0 -336
  170. package/runtime/sdk/gl/controls.ts +0 -95
  171. package/runtime/sdk/gl/physicsEvents.ts +0 -20
  172. package/runtime/sdk/gl/scenarios.ts +0 -291
  173. package/runtime/sdk/gl/state.ts +0 -6
  174. package/runtime/sdk/gl/terrainMesh.ts +0 -219
  175. package/runtime/sdk/gl/touch.ts +0 -68
  176. package/runtime/sdk/inject.ts +0 -197
  177. package/runtime/sdk/math/Mathf.ts +0 -118
  178. package/runtime/sdk/math/mat4.ts +0 -278
  179. package/runtime/sdk/math/quat.ts +0 -232
  180. package/runtime/sdk/math/vec.ts +0 -255
  181. package/runtime/sdk/net/codec.ts +0 -119
  182. package/runtime/sdk/net/core.ts +0 -384
  183. package/runtime/sdk/net/index.ts +0 -181
  184. package/runtime/sdk/net/replication.ts +0 -622
  185. package/runtime/sdk/plugins/camera.ts +0 -81
  186. package/runtime/sdk/plugins/geolocation.ts +0 -123
  187. package/runtime/sdk/plugins/oauth.ts +0 -61
  188. package/runtime/sdk/plugins/permission.ts +0 -7
  189. package/runtime/sdk/plugins/push.ts +0 -132
  190. package/runtime/sdk/plugins/qr.ts +0 -73
  191. package/runtime/sdk/plugins/service.ts +0 -47
  192. package/runtime/sdk/runtime/app.ts +0 -101
  193. package/runtime/sdk/runtime/appEvents.ts +0 -54
  194. package/runtime/sdk/runtime/channel.ts +0 -61
  195. package/runtime/sdk/runtime/clipboard.ts +0 -20
  196. package/runtime/sdk/runtime/datetime.ts +0 -329
  197. package/runtime/sdk/runtime/device.ts +0 -293
  198. package/runtime/sdk/runtime/fetch.ts +0 -77
  199. package/runtime/sdk/runtime/files.ts +0 -23
  200. package/runtime/sdk/runtime/input.ts +0 -175
  201. package/runtime/sdk/runtime/media.ts +0 -111
  202. package/runtime/sdk/runtime/misc.ts +0 -16
  203. package/runtime/sdk/runtime/net.ts +0 -36
  204. package/runtime/sdk/runtime/rpc.ts +0 -218
  205. package/runtime/sdk/runtime/service.ts +0 -83
  206. package/runtime/sdk/runtime/share.ts +0 -9
  207. package/runtime/sdk/runtime/storage.ts +0 -13
  208. package/runtime/sdk/runtime/touch.ts +0 -76
  209. package/runtime/sdk/scene/defineScene.ts +0 -1353
  210. package/runtime/sdk/scene/editorPlugins.ts +0 -110
  211. package/runtime/sdk/scene/gizmos.ts +0 -148
  212. package/runtime/sdk/scene/grammar.ts +0 -120
  213. package/runtime/sdk/scene/material.ts +0 -188
  214. package/runtime/sdk/server/auth/appConfig.ts +0 -12
  215. package/runtime/sdk/server/auth/global.ts +0 -80
  216. package/runtime/sdk/server/auth/host.ts +0 -318
  217. package/runtime/sdk/server/auth/models.ts +0 -83
  218. package/runtime/sdk/server/auth/types.ts +0 -50
  219. package/runtime/sdk/server/channel.ts +0 -56
  220. package/runtime/sdk/server/context.ts +0 -36
  221. package/runtime/sdk/server/db/defineDb.ts +0 -237
  222. package/runtime/sdk/server/db/fields.ts +0 -132
  223. package/runtime/sdk/server/db/httpTransport.ts +0 -93
  224. package/runtime/sdk/server/db/index.ts +0 -7
  225. package/runtime/sdk/server/db/marci/query.ts +0 -412
  226. package/runtime/sdk/server/db/types.ts +0 -202
  227. package/runtime/sdk/server/errors.ts +0 -12
  228. package/runtime/sdk/server/host.ts +0 -74
  229. package/runtime/sdk/server/inject.ts +0 -13
  230. package/runtime/sdk/server/runtime.ts +0 -133
  231. package/runtime/sdk/server/validate.ts +0 -87
  232. package/runtime/sdk/ui/NativeView.ts +0 -142
  233. package/runtime/sdk/ui/UI.ts +0 -39
  234. package/runtime/sdk/ui/UIBottomSheet.ts +0 -139
  235. package/runtime/sdk/ui/UIButton.ts +0 -101
  236. package/runtime/sdk/ui/UIContainer.ts +0 -60
  237. package/runtime/sdk/ui/UIImage.ts +0 -83
  238. package/runtime/sdk/ui/UIInput.ts +0 -185
  239. package/runtime/sdk/ui/UIModal.ts +0 -139
  240. package/runtime/sdk/ui/UINode.ts +0 -830
  241. package/runtime/sdk/ui/UIPager.ts +0 -362
  242. package/runtime/sdk/ui/UIPopover.ts +0 -100
  243. package/runtime/sdk/ui/UIScreen.ts +0 -123
  244. package/runtime/sdk/ui/UIScrollable.ts +0 -87
  245. package/runtime/sdk/ui/UISpacer.ts +0 -14
  246. package/runtime/sdk/ui/UITabs.ts +0 -236
  247. package/runtime/sdk/ui/UIText.ts +0 -51
  248. package/runtime/sdk/ui/UIVideo.ts +0 -88
  249. package/runtime/sdk/ui/UIVirtualizedList.ts +0 -241
  250. package/runtime/sdk/ui/UIWidget.ts +0 -127
  251. package/runtime/sdk/ui/fonts.ts +0 -13
  252. package/runtime/sdk/ui/presentable.ts +0 -117
  253. package/runtime/sdk/ui/router.ts +0 -132
  254. 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
- }