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,291 +0,0 @@
1
- // No-code scenario aspects: ready-made behaviors a scene builder attaches in the editor without
2
- // writing any code — move to a target, travel a waypoint path, spin, face something, start a GLB
3
- // animation. They are ordinary aspects (work from hand-written code too); in the scene editor the
4
- // movement ones double as `static editor` generators that draw their paths as editor gizmos
5
- // (`Gizmos.*` in rebuild() — overlay lines, never scene content; play mode never calls rebuild(),
6
- // so they cost nothing at runtime).
7
- //
8
- // Conventions shared by the family:
9
- // • targets/waypoints are scene nodes — place an Empty, then pick it in the inspector (`ref()`);
10
- // • times are seconds, angles degrees; all motion runs in `update(dt)` (play mode only);
11
- // • orientation is quaternion-composed — never euler-incremented (see math/quat.ts).
12
-
13
- import { Aspect } from "../core/Aspect"
14
- import type { FieldMeta } from "../core/fields"
15
- import type { InspectorUI } from "../core/InspectorUI"
16
- import { Vec3 } from "../math/vec"
17
- import { Quat } from "../math/quat"
18
- import { Node } from "./Node"
19
- import { Model } from "./Model"
20
- import type { Playback } from "./animation/Playback"
21
- import { Gizmos } from "../scene/gizmos"
22
-
23
- const MOVE_LINE = "#5b8ef0"
24
- const LOOK_LINE = "#d9a13f"
25
- const DEG2RAD = Math.PI / 180
26
-
27
- type LoopMode = "once" | "loop" | "pingpong"
28
-
29
- /** Normalized phase for elapsed time `t` over one trip of `duration`: once clamps at 1, loop
30
- * wraps, pingpong triangles 0→1→0. */
31
- const phase = (t: number, duration: number, mode: LoopMode): number => {
32
- if (duration <= 0) return 1
33
- const u = t / duration
34
- if (mode === "once") return Math.min(u, 1)
35
- if (mode === "loop") return u - Math.floor(u)
36
- const v = u % 2
37
- return v <= 1 ? v : 2 - v
38
- }
39
-
40
- const ease = (k: number, easing: "smooth" | "linear"): number =>
41
- easing === "smooth" ? k * k * (3 - 2 * k) : k
42
-
43
- /** A world point in `node`'s PARENT space — what `node.position` is expressed in. */
44
- const toParentLocal = (node: Node, world: Vec3): Vec3 => {
45
- const parent = node.parent
46
- return parent ? parent.worldMatrix.invert().transformPoint(world) : world
47
- }
48
-
49
- /** A path node's waypoints: its children in scene-FILE order (`_sceneChildOrder`, stamped by
50
- * defineScene — the engine's live child order is insertion-based and may differ), editor helper
51
- * nodes (`__`-prefixed) excluded. Runtime-added children append in engine order. */
52
- const pathWaypoints = (path: Node): Node[] => {
53
- const children = path.children.filter((c) => !(c.name ?? "").startsWith("__"))
54
- const order = (path as { _sceneChildOrder?: string[] })._sceneChildOrder
55
- if (!order) return children
56
- const byName = new Map(children.map((c) => [ c.name, c ]))
57
- const sorted: Node[] = []
58
- for (const n of order) { const c = byName.get(n); if (c) sorted.push(c) }
59
- const seen = new Set(sorted)
60
- for (const c of children) if (!seen.has(c)) sorted.push(c)
61
- return sorted
62
- }
63
-
64
- // ---- the aspects ---------------------------------------------------------------------------------
65
-
66
- /** Travel from the node's starting position to a target node in `duration` seconds. */
67
- export class MoveTo extends Aspect<"moveTo"> {
68
- /** Where to travel — place an Empty node and pick it. */
69
- target: Node | null = null
70
-
71
- /** Seconds for the full trip. */
72
- duration = 2
73
-
74
- /** Seconds to wait before starting. */
75
- delay = 0
76
-
77
- /** once = stop at the target · loop = restart from the start point · pingpong = back and forth. */
78
- mode: LoopMode = "once"
79
-
80
- /** Motion curve. */
81
- easing: "smooth" | "linear" = "smooth"
82
-
83
- static readonly aspect = "moveTo"
84
- static editor = { rebuild: true }
85
- static fields: FieldMeta<MoveTo> = {
86
- target: { editor: "node" },
87
- duration: { min: 0.05, max: 120, step: 0.05 },
88
- delay: { min: 0, max: 120, step: 0.05 },
89
- mode: { options: [ "once", "loop", "pingpong" ] },
90
- easing: { options: [ "smooth", "linear" ] },
91
- }
92
-
93
- private _t = 0
94
- private _start: Vec3 | null = null
95
-
96
- update(dt: number): void {
97
- if (!this.target) return
98
- this._start ??= this.node.position
99
- this._t += dt
100
- const k = ease(phase(Math.max(0, this._t - this.delay), this.duration, this.mode), this.easing)
101
- const end = toParentLocal(this.node, this.target.worldPosition)
102
- this.node.position = new Vec3(this._start).add(end.sub(this._start).scale(k))
103
- }
104
-
105
- /** Editor-only path line (edit mode; play mode never calls this). */
106
- rebuild(): void {
107
- if (!this.target) return
108
- // the target is an empty — its own anchored marker shows the endpoint
109
- Gizmos.line(this.node.worldPosition, this.target.worldPosition, { color: MOVE_LINE })
110
- }
111
- }
112
-
113
- /** Travel through the children of a path node (add an Empty per waypoint) in `duration` seconds.
114
- * `loop` runs the circuit closed (last → first); `pingpong` goes back and forth along the open
115
- * path; `once` stops at the last waypoint. */
116
- export class FollowPath extends Aspect<"followPath"> {
117
- /** A node whose CHILDREN are the waypoints, in file order. */
118
- path: Node | null = null
119
-
120
- /** Seconds for one full pass of the path. */
121
- duration = 6
122
-
123
- /** Seconds to wait before starting. */
124
- delay = 0
125
-
126
- mode: LoopMode = "loop"
127
-
128
- /** Turn to face the direction of travel. */
129
- orient = true
130
-
131
- /** Which local axis leads when orienting. */
132
- forward: "-z" | "z" | "x" | "-x" = "-z"
133
-
134
- static readonly aspect = "followPath"
135
- static editor = { rebuild: true }
136
- static fields: FieldMeta<FollowPath> = {
137
- path: { editor: "node" },
138
- duration: { min: 0.05, max: 300, step: 0.05 },
139
- delay: { min: 0, max: 120, step: 0.05 },
140
- mode: { options: [ "loop", "once", "pingpong" ] },
141
- forward: { options: [ "-z", "z", "x", "-x" ] },
142
- }
143
-
144
- private _t = 0
145
-
146
- private _points(): Vec3[] {
147
- if (!this.path) return []
148
- const points = pathWaypoints(this.path).map((c) => c.worldPosition)
149
- if (this.mode === "loop" && points.length > 1) points.push(points[0]!.clone())
150
- return points
151
- }
152
-
153
- update(dt: number): void {
154
- const points = this._points()
155
- if (points.length < 2) return
156
- this._t += dt
157
- const k = phase(Math.max(0, this._t - this.delay), this.duration, this.mode === "loop" ? "loop" : this.mode)
158
-
159
- const lengths: number[] = []
160
- let total = 0
161
- for (let i = 0; i < points.length - 1; i++) {
162
- const l = points[i + 1]!.sub(points[i]!).length()
163
- lengths.push(l)
164
- total += l
165
- }
166
- if (total <= 0) return
167
-
168
- let dist = k * total
169
- let i = 0
170
- while (i < lengths.length - 1 && dist > lengths[i]!) { dist -= lengths[i]!; i++ }
171
- const a = points[i]!
172
- const b = points[i + 1]!
173
- const seg = b.sub(a)
174
- const u = lengths[i]! > 0 ? Math.min(dist / lengths[i]!, 1) : 0
175
- const world = a.add(seg.scale(u))
176
- this.node.position = toParentLocal(this.node, world)
177
- if (this.orient && seg.lengthSq() > 1e-12) this.node.lookAt(world.add(seg), this.forward)
178
- }
179
-
180
- /** Editor-only waypoint polyline (edit mode; play mode never calls this). */
181
- rebuild(): void {
182
- const points = this._points() // loop mode already appends the first point again
183
- Gizmos.polyline(points, { color: MOVE_LINE }) // waypoints are empties: they carry their own markers
184
- }
185
- }
186
-
187
- /** Continuous rotation about one of the node's local axes, degrees per second. */
188
- export class Spin extends Aspect<"spin"> {
189
- /** Degrees per second. */
190
- speed = 45
191
-
192
- /** Local axis to spin around. */
193
- axis: "x" | "y" | "z" = "y"
194
-
195
- static readonly aspect = "spin"
196
- static fields: FieldMeta<Spin> = {
197
- speed: { min: -720, max: 720, step: 5 },
198
- axis: { options: [ "x", "y", "z" ] },
199
- }
200
-
201
- update(dt: number): void {
202
- const axis: [number, number, number] = this.axis === "x" ? [ 1, 0, 0 ] : this.axis === "y" ? [ 0, 1, 0 ] : [ 0, 0, 1 ]
203
- // compose in quaternion space about the LOCAL axis (q ⊗ dq) — never euler-increment
204
- this.node.quaternion = this.node.quaternion.mul(Quat.fromAxisAngle(axis, this.speed * dt * DEG2RAD))
205
- }
206
- }
207
-
208
- /** Keep the node facing a target node (a turret tracking, a signpost, a camera aimed at a hero).
209
- * Accessor `node.facing` — NOT `lookAt`, which would shadow the `Node.lookAt()` method. */
210
- export class LookAt extends Aspect<"facing"> {
211
- /** The node to face. */
212
- target: Node | null = null
213
-
214
- /** Which local axis points at the target. */
215
- forward: "-z" | "z" | "x" | "-x" = "-z"
216
-
217
- /** 0 = snap instantly; otherwise seconds of turn lag (bigger = slower, smoother). */
218
- smoothing = 0
219
-
220
- static readonly aspect = "facing"
221
- static editor = { rebuild: true }
222
- static fields: FieldMeta<LookAt> = {
223
- target: { editor: "node" },
224
- forward: { options: [ "-z", "z", "x", "-x" ] },
225
- smoothing: { min: 0, max: 5, step: 0.05 },
226
- }
227
-
228
- update(dt: number): void {
229
- if (!this.target) return
230
- const point = this.target.worldPosition
231
- if (this.smoothing <= 0) { this.node.lookAt(point, this.forward); return }
232
- const q0 = this.node.quaternion
233
- this.node.lookAt(point, this.forward)
234
- const q1 = this.node.quaternion
235
- this.node.quaternion = q0.slerp(q1, 1 - Math.exp(-dt / this.smoothing))
236
- }
237
-
238
- /** Editor-only sight line (edit mode; play mode never calls this). */
239
- rebuild(): void {
240
- if (!this.target) return
241
- Gizmos.line(this.node.worldPosition, this.target.worldPosition, { color: LOOK_LINE })
242
- }
243
- }
244
-
245
- /** Start a GLB animation clip when the scene runs. Attach to a `model:` node; the inspector card
246
- * lists the model's clips live and can preview them while editing. */
247
- export class PlayAnimation extends Aspect<"playAnimation", Model> {
248
- /** Clip name; empty = the model's first clip. */
249
- clip = ""
250
-
251
- loop = true
252
-
253
- /** Playback rate (1 = authored speed). */
254
- speed = 1
255
-
256
- static readonly aspect = "playAnimation"
257
- static fields: FieldMeta<PlayAnimation> = {
258
- speed: { min: 0.05, max: 5, step: 0.05 },
259
- }
260
-
261
- private _preview?: Playback
262
-
263
- onAttach(): void {
264
- const anim = (this.node as Model | undefined)?.anim
265
- if (!anim) return
266
- if (this.loop) anim.playLoop(this.clip === "" ? 0 : this.clip, { speed: this.speed })
267
- else anim.play(this.clip === "" ? 0 : this.clip, { speed: this.speed })
268
- }
269
-
270
- static inspector(ui: InspectorUI, a: PlayAnimation): void {
271
- const anim = (a.node as Model | undefined)?.anim
272
- const clips = anim ? anim.clips : []
273
- if (clips.length === 0) {
274
- ui.warn("No animation clips — attach this to a GLB model node")
275
- return
276
- }
277
- ui.select("clip", clips.map((c) => c.name))
278
- ui.auto("loop", "speed")
279
- if (anim) {
280
- const clip = a.clip === "" ? clips[0]?.name : a.clip
281
- const playing = a._preview?.playing === true
282
- if (ui.button(playing ? "Restart preview" : "Preview")) {
283
- if (a.loop) { anim.playLoop(clip, { speed: a.speed }); a._preview = undefined }
284
- else a._preview = anim.play(clip, { speed: a.speed, restart: true })
285
- }
286
- if (playing && ui.button("Stop")) { anim.stop(); a._preview = undefined }
287
- const duration = clips.find((c) => c.name === clip)?.duration
288
- ui.info(playing ? "previewing…" : duration !== undefined ? `${duration.toFixed(2)}s` : "")
289
- }
290
- }
291
- }
@@ -1,6 +0,0 @@
1
- // Tiny shared module to break the Node ↔ Scene ↔ touch dependency cycle. The active 3D scene is set
2
- // here by Scene.open()/close() and read by the touch dispatch system.
3
-
4
- import type { Scene } from "./Scene"
5
-
6
- export const glState: { activeScene: Scene | null, frame: number } = { activeScene: null, frame: 0 }
@@ -1,219 +0,0 @@
1
- // The terrain grid as geometry, in TypeScript — used two ways (docs/terrain-plan.md §1.3, §3.5):
2
- // • `heightAt` / `normalAt` — the SDK's own queries, identical on every host (bilinear on the SAME
3
- // triangle split the engines draw and collide);
4
- // • `buildTerrainChunk` — chunk meshes for hosts WITHOUT `_creator.terrainCreate` (web-lite, headless,
5
- // a wasm/AAR/Apple build predating the feature): the same grid → chunk → triangles rule
6
- // creator-gl/src/terrain.cpp applies (the i00–i11 diagonal, CCW seen from +Y, a triangle only when
7
- // its three samples are valid), as plain Geometry for setMesh. Normals use central differences over
8
- // the FULL grid (seamless chunks); the tangent frame is then the host's arbitrary one, so normal maps
9
- // read wrong on those hosts — the accepted fallback cost.
10
- // Keep the hole / split rule in step with terrain.cpp buildChunk and Jolt's HeightFieldShape.
11
-
12
- import { Geometry } from "./Geometry"
13
-
14
- export type TerrainGrid = {
15
- sizeX: number
16
- sizeZ: number
17
- cellSize: number
18
- /** sizeX × sizeZ heights, row-major (z rows). */
19
- heights: Float32Array
20
- /** One byte per sample, 1 = hole; null = no holes. */
21
- holes: Uint8Array | null
22
- }
23
-
24
- const clampI = (v: number, hi: number): number => (v < 0 ? 0 : v > hi ? hi : v)
25
-
26
- /** The sample at integer (x, z), clamped to the grid. */
27
- export const sampleAt = (g: TerrainGrid, x: number, z: number): number =>
28
- g.heights[clampI(z, g.sizeZ - 1) * g.sizeX + clampI(x, g.sizeX - 1)]
29
-
30
- export const isHole = (g: TerrainGrid, x: number, z: number): boolean =>
31
- g.holes !== null && g.holes[z * g.sizeX + x] !== 0
32
-
33
- /** Surface normal at a sample from central differences (one-sided at the borders). Writes `out` (xyz). */
34
- export const normalAt = (g: TerrainGrid, x: number, z: number, out: Float32Array | number[] = [ 0, 0, 0 ]): typeof out => {
35
- const x0 = x > 0 ? x - 1 : x, x1 = x + 1 < g.sizeX ? x + 1 : x
36
- const z0 = z > 0 ? z - 1 : z, z1 = z + 1 < g.sizeZ ? z + 1 : z
37
- const dx = (sampleAt(g, x1, z) - sampleAt(g, x0, z)) / ((x1 - x0) * g.cellSize)
38
- const dz = (sampleAt(g, x, z1) - sampleAt(g, x, z0)) / ((z1 - z0) * g.cellSize)
39
- const len = Math.hypot(dx, 1, dz)
40
- out[0] = -dx / len; out[1] = 1 / len; out[2] = -dz / len
41
- return out
42
- }
43
-
44
- /**
45
- * Height at a LOCAL (x, z) in metres, interpolated on the drawn triangles (the i00–i11 diagonal), so a
46
- * character's feet and a raycast agree with what is rendered to the millimetre. NaN inside a hole
47
- * triangle or outside the grid.
48
- */
49
- export const heightAt = (g: TerrainGrid, x: number, z: number): number => {
50
- const fx = x / g.cellSize, fz = z / g.cellSize
51
- if (!(fx >= 0 && fz >= 0 && fx <= g.sizeX - 1 && fz <= g.sizeZ - 1)) return NaN
52
- let ix = Math.floor(fx), iz = Math.floor(fz)
53
- if (ix >= g.sizeX - 1) ix = g.sizeX - 2
54
- if (iz >= g.sizeZ - 1) iz = g.sizeZ - 2
55
- const u = fx - ix, v = fz - iz
56
- const i00 = iz * g.sizeX + ix
57
- const h00 = g.heights[i00], h10 = g.heights[i00 + 1], h01 = g.heights[i00 + g.sizeX], h11 = g.heights[i00 + g.sizeX + 1]
58
- if (g.holes !== null) {
59
- const holes = g.holes
60
- if (holes[i00] || holes[i00 + g.sizeX + 1]) return NaN
61
- // lower-left triangle (i00, i01, i11) when v >= u, else upper-right (i00, i11, i10)
62
- if (v >= u ? holes[i00 + g.sizeX] !== 0 : holes[i00 + 1] !== 0) return NaN
63
- }
64
- // plane of the triangle the point falls in
65
- return v >= u
66
- ? h00 + (h11 - h01) * u + (h01 - h00) * v
67
- : h00 + (h10 - h00) * u + (h11 - h10) * v
68
- }
69
-
70
- /** The geometric normal at a LOCAL (x, z): the drawn triangle's normal (not the smoothed vertex one). */
71
- export const faceNormalAt = (g: TerrainGrid, x: number, z: number, out: Float32Array | number[] = [ 0, 0, 0 ]): typeof out => {
72
- const fx = x / g.cellSize, fz = z / g.cellSize
73
- let ix = Math.floor(fx), iz = Math.floor(fz)
74
- ix = clampI(ix, g.sizeX - 2); iz = clampI(iz, g.sizeZ - 2)
75
- const u = fx - ix, v = fz - iz
76
- const i00 = iz * g.sizeX + ix
77
- const h00 = g.heights[i00], h10 = g.heights[i00 + 1], h01 = g.heights[i00 + g.sizeX], h11 = g.heights[i00 + g.sizeX + 1]
78
- const c = g.cellSize
79
- // gradients of the two planes (see heightAt)
80
- const dx = (v >= u ? h11 - h01 : h10 - h00) / c
81
- const dz = (v >= u ? h01 - h00 : h11 - h10) / c
82
- const len = Math.hypot(dx, 1, dz)
83
- out[0] = -dx / len; out[1] = 1 / len; out[2] = -dz / len
84
- return out
85
- }
86
-
87
- /** Chunks along X and Z for a grid: a chunk covers `chunk` cells (chunk + 1 samples). */
88
- export const chunkCounts = (g: TerrainGrid, chunk: number): [number, number] =>
89
- [ Math.floor((g.sizeX - 2) / chunk) + 1, Math.floor((g.sizeZ - 2) / chunk) + 1 ]
90
-
91
- /**
92
- * One chunk as a Geometry (positions, smooth normals, UV0 = local XZ metres, UV1 = the terrain's unit
93
- * square, u16 indices), or null when every cell of it is a hole.
94
- */
95
- export const buildTerrainChunk = (g: TerrainGrid, cx: number, cz: number, chunk: number): Geometry | null => {
96
- const x0 = cx * chunk, z0 = cz * chunk
97
- const x1 = Math.min(x0 + chunk, g.sizeX - 1), z1 = Math.min(z0 + chunk, g.sizeZ - 1)
98
- const nx = x1 - x0 + 1, nz = z1 - z0 + 1
99
- const idx: number[] = []
100
- for (let z = z0; z < z1; z++) {
101
- for (let x = x0; x < x1; x++) {
102
- const i00 = (z - z0) * nx + (x - x0), i10 = i00 + 1, i01 = i00 + nx, i11 = i01 + 1
103
- const v00 = !isHole(g, x, z), v10 = !isHole(g, x + 1, z), v01 = !isHole(g, x, z + 1), v11 = !isHole(g, x + 1, z + 1)
104
- if (!(v00 && v11)) continue
105
- if (v01) idx.push(i00, i01, i11)
106
- if (v10) idx.push(i00, i11, i10)
107
- }
108
- }
109
- if (idx.length === 0) return null
110
- const count = nx * nz
111
- const vertices = new Float32Array(count * 3)
112
- const normals = new Float32Array(count * 3)
113
- const uv = new Float32Array(count * 2)
114
- const uv1 = new Float32Array(count * 2)
115
- const n = [ 0, 0, 0 ]
116
- const invX = 1 / (g.sizeX - 1), invZ = 1 / (g.sizeZ - 1)
117
- for (let z = z0; z <= z1; z++) {
118
- for (let x = x0; x <= x1; x++) {
119
- const i = (z - z0) * nx + (x - x0)
120
- vertices[i * 3] = x * g.cellSize
121
- vertices[i * 3 + 1] = g.heights[z * g.sizeX + x]
122
- vertices[i * 3 + 2] = z * g.cellSize
123
- normalAt(g, x, z, n)
124
- normals[i * 3] = n[0]; normals[i * 3 + 1] = n[1]; normals[i * 3 + 2] = n[2]
125
- uv[i * 2] = x * g.cellSize; uv[i * 2 + 1] = z * g.cellSize
126
- uv1[i * 2] = x * invX; uv1[i * 2 + 1] = z * invZ
127
- }
128
- }
129
- const geom = new Geometry(vertices, normals, Uint16Array.from(idx), uv)
130
- geom.uv1 = uv1
131
- return geom
132
- }
133
-
134
- // ---- ray queries ---------------------------------------------------------------------------------
135
-
136
- export type GridHit = { x: number, y: number, z: number, distance: number, nx: number, ny: number, nz: number }
137
-
138
- /** Möller–Trumbore against one triangle; the distance along the ray, or -1. */
139
- const rayTriangle = (
140
- ox: number, oy: number, oz: number, dx: number, dy: number, dz: number,
141
- ax: number, ay: number, az: number, bx: number, by: number, bz: number, qx: number, qy: number, qz: number,
142
- ): number => {
143
- const e1x = bx - ax, e1y = by - ay, e1z = bz - az
144
- const e2x = qx - ax, e2y = qy - ay, e2z = qz - az
145
- const px = dy * e2z - dz * e2y, py = dz * e2x - dx * e2z, pz = dx * e2y - dy * e2x
146
- const det = e1x * px + e1y * py + e1z * pz
147
- if (Math.abs(det) < 1e-12) return -1
148
- const inv = 1 / det
149
- const tx = ox - ax, ty = oy - ay, tz = oz - az
150
- const u = (tx * px + ty * py + tz * pz) * inv
151
- if (u < -1e-6 || u > 1 + 1e-6) return -1
152
- const qxx = ty * e1z - tz * e1y, qyy = tz * e1x - tx * e1z, qzz = tx * e1y - ty * e1x
153
- const v = (dx * qxx + dy * qyy + dz * qzz) * inv
154
- if (v < -1e-6 || u + v > 1 + 1e-6) return -1
155
- const t = (e2x * qxx + e2y * qyy + e2z * qzz) * inv
156
- return t >= 0 ? t : -1
157
- }
158
-
159
- /**
160
- * Nearest hit of a LOCAL ray (origin + direction, metres) on the drawn triangles — the same
161
- * i00–i11 split and hole rule as `heightAt`, so a pick lands on what is rendered. Walks the cells
162
- * the ray crosses (2D DDA on XZ), testing each cell's two triangles; holes let the ray through.
163
- * Null when it misses (or never enters the grid within `maxDistance`).
164
- */
165
- export const gridRaycast = (g: TerrainGrid, ox: number, oy: number, oz: number, dx: number, dy: number, dz: number, maxDistance = 1e5): GridHit | null => {
166
- const c = g.cellSize, W = (g.sizeX - 1) * c, D = (g.sizeZ - 1) * c
167
- let t0 = 0, t1 = maxDistance
168
- const clip = (o: number, d: number, hi: number): boolean => {
169
- if (Math.abs(d) < 1e-12) return o >= 0 && o <= hi
170
- let a = -o / d, b = (hi - o) / d
171
- if (a > b) { const s = a; a = b; b = s }
172
- if (a > t0) t0 = a
173
- if (b < t1) t1 = b
174
- return t0 <= t1
175
- }
176
- if (!clip(ox, dx, W) || !clip(oz, dz, D)) return null
177
- const sx = ox + dx * t0, sz = oz + dz * t0
178
- let ix = Math.min(g.sizeX - 2, Math.max(0, Math.floor(sx / c)))
179
- let iz = Math.min(g.sizeZ - 2, Math.max(0, Math.floor(sz / c)))
180
- const stepX = dx > 1e-12 ? 1 : dx < -1e-12 ? -1 : 0
181
- const stepZ = dz > 1e-12 ? 1 : dz < -1e-12 ? -1 : 0
182
- const tDeltaX = stepX ? Math.abs(c / dx) : Infinity, tDeltaZ = stepZ ? Math.abs(c / dz) : Infinity
183
- let tMaxX = stepX > 0 ? t0 + ((ix + 1) * c - sx) / dx : stepX < 0 ? t0 + (ix * c - sx) / dx : Infinity
184
- let tMaxZ = stepZ > 0 ? t0 + ((iz + 1) * c - sz) / dz : stepZ < 0 ? t0 + (iz * c - sz) / dz : Infinity
185
- const holes = g.holes
186
- let t = t0
187
- for (let guard = 0; guard < g.sizeX + g.sizeZ + 2 && t <= t1 + 1e-9; guard++) {
188
- const i00 = iz * g.sizeX + ix, i10 = i00 + 1, i01 = i00 + g.sizeX, i11 = i01 + 1
189
- const x0 = ix * c, x1 = x0 + c, z0 = iz * c, z1 = z0 + c
190
- const h00 = g.heights[i00], h10 = g.heights[i10], h01 = g.heights[i01], h11 = g.heights[i11]
191
- const ok00 = !holes || holes[i00] === 0, ok11 = !holes || holes[i11] === 0
192
- let best = -1, tri = 0
193
- if (ok00 && ok11) {
194
- // lower-left (i00, i01, i11) and upper-right (i00, i11, i10) — both CCW seen from +Y
195
- if (!holes || holes[i01] === 0) {
196
- const d = rayTriangle(ox, oy, oz, dx, dy, dz, x0, h00, z0, x0, h01, z1, x1, h11, z1)
197
- if (d >= 0 && (best < 0 || d < best)) { best = d; tri = 0 }
198
- }
199
- if (!holes || holes[i10] === 0) {
200
- const d = rayTriangle(ox, oy, oz, dx, dy, dz, x0, h00, z0, x1, h11, z1, x1, h10, z0)
201
- if (d >= 0 && (best < 0 || d < best)) { best = d; tri = 1 }
202
- }
203
- }
204
- if (best >= 0 && best <= t1 + 1e-6) {
205
- // the hit triangle's normal
206
- let ax: number, ay: number, az: number, bx: number, by: number, bz: number
207
- if (tri === 0) { ax = 0; ay = h01 - h00; az = c; bx = c; by = h11 - h00; bz = c }
208
- else { ax = c; ay = h11 - h00; az = c; bx = c; by = h10 - h00; bz = 0 }
209
- let nx = ay * bz - az * by, ny = az * bx - ax * bz, nz = ax * by - ay * bx
210
- const l = Math.hypot(nx, ny, nz) || 1
211
- nx /= l; ny /= l; nz /= l
212
- return { x: ox + dx * best, y: oy + dy * best, z: oz + dz * best, distance: best, nx, ny, nz }
213
- }
214
- if (tMaxX < tMaxZ) { t = tMaxX; ix += stepX; tMaxX += tDeltaX }
215
- else { t = tMaxZ; iz += stepZ; tMaxZ += tDeltaZ }
216
- if (ix < 0 || iz < 0 || ix > g.sizeX - 2 || iz > g.sizeZ - 2) break
217
- }
218
- return null
219
- }
@@ -1,68 +0,0 @@
1
- // 3D pointer dispatch. Registers the host touch callbacks lazily (first listener) and routes hits to
2
- // the struck Node and to the active scene's listeners. Ported from worker/src/TouchEventSystem.ts.
3
-
4
- import { ClickEvent, TouchStartEvent, type TrackHandler } from "../runtime/touch"
5
- import { nodeRegistry } from "./Node"
6
- import { glState } from "./state"
7
-
8
- // Hits inside a loaded model (GLB) land on sub-entities the user never created a Node for — walk up
9
- // the transform hierarchy to the nearest entity someone actually holds (the model's Node).
10
- const resolveHitNode = (entityId: number) => {
11
- let node = nodeRegistry.get(entityId)
12
- for (let id = entityId, i = 0; !node && i < 32; i++) {
13
- id = _creator.getParent(id)
14
- if (id === 0) break
15
- node = nodeRegistry.get(id)
16
- }
17
- return node ?? null
18
- }
19
-
20
- const onTouchEnd = (pointerId: number, clientX: number, clientY: number, entityId: number): TrackHandler | null => {
21
- const scene = glState.activeScene
22
- if (!scene) return null
23
- const ev = new ClickEvent<any>(clientX, clientY, pointerId)
24
- if (entityId > 0) {
25
- const node = resolveHitNode(entityId)
26
- ev.target = node
27
- if (node) node._dispatchEvent("click", ev)
28
- } else {
29
- ev.target = null
30
- }
31
- for (const listener of scene._clickListeners.slice()) listener(ev)
32
- return null
33
- }
34
-
35
- const onTouchStart = (pointerId: number, clientX: number, clientY: number, entityId: number): TrackHandler | null => {
36
- const scene = glState.activeScene
37
- if (!scene) return null
38
- const ev = new TouchStartEvent<any>(clientX, clientY, pointerId)
39
- if (entityId > 0) {
40
- const node = resolveHitNode(entityId)
41
- ev.target = node
42
- if (node) {
43
- node._dispatchEvent("touchstart", ev)
44
- if (ev._trackHandler) return ev._trackHandler
45
- }
46
- } else {
47
- ev.target = null
48
- }
49
- for (const listener of scene._touchStartListeners.slice()) {
50
- listener(ev)
51
- if (ev._trackHandler) return ev._trackHandler
52
- }
53
- return null
54
- }
55
-
56
- let touchStartRegistered = false
57
- export const registerTouchStartEvent = (): void => {
58
- if (touchStartRegistered) return
59
- touchStartRegistered = true
60
- _creatorUI.registerTouchStartEvent(onTouchStart)
61
- }
62
-
63
- let touchEndRegistered = false
64
- export const registerTouchEndEvent = (): void => {
65
- if (touchEndRegistered) return
66
- touchEndRegistered = true
67
- _creatorUI.registerTouchEndEvent(onTouchEnd)
68
- }