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,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
- }