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,83 +0,0 @@
1
- // 2D pointer dispatch + track (drag) system. The host (createViewer2d canvas / Creator2DView) delivers
2
- // raw pointer phases via _creator2d.setOnPointer; this module hit-tests in the active scene and routes
3
- // to the struck node and the scene's listeners. Mirrors the 3D gl/touch.ts, but the hit-test is done
4
- // here (screen→world via the camera, then a physics overlap), not by the host.
5
- //
6
- // What can be hit: a node with a Physics2D shape (precise) → its 'touchstart'/'click' events; the
7
- // active Scene2D always gets the event too (target = the hit node or null). A node listener can call
8
- // ev.track({ onMove, onEnd, onCancel }) to capture the rest of the gesture (drag/pan).
9
-
10
- import { ClickEvent, TouchStartEvent, type TrackHandler } from "../runtime/touch"
11
- import { Physics2D } from "./Physics2D"
12
- import { Scene2D } from "./Scene2D"
13
- import type { Node2D } from "./Node2D"
14
-
15
- type Active = { tracker?: TrackHandler, lastX: number, lastY: number }
16
- const active = new Map<number, Active>()
17
-
18
- let registered = false
19
- /** Register the host pointer callback once, on the first click/touchstart listener (node or scene). */
20
- export const ensurePointerEvents = (): void => {
21
- if (registered) return
22
- registered = true
23
- _creator2d.setOnPointer((phase, id, x, y) => {
24
- if (phase === 0) onDown(id, x, y)
25
- else if (phase === 1) onMove(id, x, y)
26
- else if (phase === 2) onUp(id, x, y)
27
- else onCancel(id)
28
- })
29
- }
30
-
31
- // Hit-test: screen (logical px) → world → topmost physics body at the point.
32
- const hitTest = (scene: Scene2D, sx: number, sy: number): { node: Node2D | null, wx: number, wy: number } => {
33
- const w = scene.camera.screenToWorld(sx, sy)
34
- return { node: Physics2D.overlapPoint(w), wx: w.x, wy: w.y }
35
- }
36
-
37
- const onDown = (id: number, sx: number, sy: number): void => {
38
- const scene = Scene2D.active
39
- if (!scene) return
40
- const { node, wx, wy } = hitTest(scene, sx, sy)
41
- const ev = new TouchStartEvent<Node2D | null>(sx, sy, id)
42
- ev.worldX = wx; ev.worldY = wy
43
- ev.target = node
44
-
45
- let tracker: TrackHandler | undefined
46
- if (node) { node._dispatchEvent("touchstart", ev); tracker = ev._trackHandler }
47
- if (!tracker) {
48
- for (const l of scene._touchStartListeners.slice()) {
49
- l(ev)
50
- if (ev._trackHandler) { tracker = ev._trackHandler; break }
51
- }
52
- }
53
- active.set(id, { tracker, lastX: sx, lastY: sy })
54
- }
55
-
56
- const onMove = (id: number, sx: number, sy: number): void => {
57
- const a = active.get(id)
58
- if (!a) return
59
- a.tracker?.onMove?.({ clientX: sx, clientY: sy, deltaX: sx - a.lastX, deltaY: sy - a.lastY })
60
- a.lastX = sx; a.lastY = sy
61
- }
62
-
63
- const onUp = (id: number, sx: number, sy: number): void => {
64
- const a = active.get(id)
65
- active.delete(id)
66
- a?.tracker?.onEnd?.({ clientX: sx, clientY: sy, deltaX: a ? sx - a.lastX : 0, deltaY: a ? sy - a.lastY : 0 })
67
-
68
- // A pointer-up is also a click, dispatched at the up location (independent of any track).
69
- const scene = Scene2D.active
70
- if (!scene) return
71
- const { node, wx, wy } = hitTest(scene, sx, sy)
72
- const ev = new ClickEvent<Node2D | null>(sx, sy, id)
73
- ev.worldX = wx; ev.worldY = wy
74
- ev.target = node
75
- if (node) node._dispatchEvent("click", ev)
76
- for (const l of scene._clickListeners.slice()) l(ev)
77
- }
78
-
79
- const onCancel = (id: number): void => {
80
- const a = active.get(id)
81
- active.delete(id)
82
- a?.tracker?.onCancel?.()
83
- }
@@ -1,160 +0,0 @@
1
- // The scene camera — a Node (so position / lookAt / transform all work) plus projection reads and
2
- // screen↔world ray helpers. Created and attached by Scene. Ported from worker/src/components/Camera.ts.
3
-
4
- import { Vec3 } from "../math/vec"
5
- import { Aspect } from "../core/Aspect"
6
- import { Node } from "./Node"
7
- import { Ray } from "./Ray"
8
-
9
- /** @internal `camera.follow(node)`: copies the node's world pose onto the camera every LATE frame
10
- * (order 1000 — after every other aspect has moved things). One per camera; lives on the target. */
11
- export class CameraFollowRig extends Aspect<"__cameraFollow"> {
12
- static readonly aspect = "__cameraFollow"
13
- /** @internal */ _camera!: Camera
14
- constructor() { super(); this.updateOrder = 1000 }
15
- update(): void {
16
- const cam = this._camera
17
- cam.position = this.node.worldPosition
18
- cam.quaternion = this.node.worldQuaternion
19
- }
20
- }
21
-
22
- /** Projection every host starts a scene with (creator-gl scene.cpp / the lite core agree on these). */
23
- export const CAMERA_DEFAULTS = { fov: 60, near: 0.01, far: 1000 }
24
-
25
- export class Camera extends Node {
26
- private _sceneId = -1
27
- /** Live projection matrix (updated by the host when the viewport/fov changes). */
28
- readonly projectionMatrix = new Float32Array(16)
29
-
30
- // Projection the app asked for (host defaults until it does). The host owns the aspect and
31
- // re-applies these on every viewport change, so they are set, not maintained.
32
- private _fov = CAMERA_DEFAULTS.fov
33
- private _near = CAMERA_DEFAULTS.near
34
- private _far = CAMERA_DEFAULTS.far
35
- /** Stay quiet until the app actually sets something — hosts may be configured with their own
36
- * defaults (the headless core takes a `fov` option) and a blind push would overwrite them. */
37
- private _projSet = false
38
-
39
- // Exposure the app asked for (filament's physical defaults until it does): f/16, 1/125 s, ISO 100 =
40
- // EV100 15, bright sunlight — what every light intensity in the docs is tuned against.
41
- private _exposure = { aperture: 16, shutterSpeed: 1 / 125, iso: 100 }
42
- private _ev = 0
43
- private _exposureSet = false
44
-
45
- /** @internal — Scene wires the camera to its native scene id + projection callback. */
46
- _attach(sceneId: number): void {
47
- this._sceneId = sceneId
48
- _creator.attachCameraToAR(sceneId, this.id, (mat) => {
49
- for (let i = 0; i < 16; i++) this.projectionMatrix[i] = mat[i]
50
- })
51
- if (this._projSet) this._applyProjection()
52
- if (this._exposureSet) this._applyExposure()
53
- }
54
-
55
- /** Exposure compensation in stops (default 0): +1 doubles how bright the scene renders, −2
56
- * quarters it. The one exposure knob most games need — a dark interior, a flash of white, or eye
57
- * adaptation when the sun swings into view (drop it, and the sun stays at the display's peak
58
- * while everything else darkens). Scene light follows it; particle `emissive` is post-exposure
59
- * and does not, so a fireball keeps its on-screen brightness. Cheap to animate every frame. */
60
- get exposureCompensation(): number { return this._ev }
61
- set exposureCompensation(stops: number) {
62
- this._ev = Number.isFinite(stops) ? stops : 0
63
- this._exposureSet = true
64
- this._applyExposure()
65
- }
66
-
67
- /** The physical camera: `aperture` in f-stops, `shutterSpeed` in seconds, `iso`, plus the same
68
- * `compensation` in stops — any subset, one call. `camera.setExposure({ iso: 400 })` is two stops
69
- * brighter than the default f/16 · 1/125 · ISO 100. */
70
- setExposure(exposure: { aperture?: number, shutterSpeed?: number, iso?: number, compensation?: number }): this {
71
- if (exposure.aperture !== undefined) this._exposure.aperture = exposure.aperture
72
- if (exposure.shutterSpeed !== undefined) this._exposure.shutterSpeed = exposure.shutterSpeed
73
- if (exposure.iso !== undefined) this._exposure.iso = exposure.iso
74
- if (exposure.compensation !== undefined) this._ev = exposure.compensation
75
- this._exposureSet = true
76
- this._applyExposure()
77
- return this
78
- }
79
-
80
- /** Hosts that predate the call keep the fixed default (older wasm / native builds). */
81
- private _applyExposure(): void {
82
- if (this._sceneId < 0) return
83
- const e = this._exposure
84
- _creator.setCameraExposure?.(this._sceneId, e.aperture, e.shutterSpeed, e.iso * 2 ** this._ev)
85
- }
86
-
87
- /** Vertical field of view in degrees (default 60) — a smaller angle is a longer lens. */
88
- get fov(): number { return _creator.getCameraFov(this._sceneId, 0) }
89
- set fov(degrees: number) { this._fov = degrees; this._projSet = true; this._applyProjection() }
90
- get horizontalFov(): number { return _creator.getCameraFov(this._sceneId, 1) }
91
-
92
- /** Near clip distance (default 0.01) — nothing closer than this draws. */
93
- get near(): number { return this._near }
94
- set near(distance: number) { this._near = distance; this._projSet = true; this._applyProjection() }
95
-
96
- /** Far clip distance / view range (default 1000) — geometry past it is culled. Shadows keep
97
- * their own (shorter) range, so a long view distance doesn't cost shadow sharpness. */
98
- get far(): number { return this._far }
99
- set far(distance: number) { this._far = distance; this._projSet = true; this._applyProjection() }
100
-
101
- /** Set any of fov / near / far in one call: `camera.setProjection({ fov: 45, far: 5000 })`. */
102
- setProjection(projection: { fov?: number, near?: number, far?: number }): this {
103
- if (projection.fov !== undefined) this._fov = projection.fov
104
- if (projection.near !== undefined) this._near = projection.near
105
- if (projection.far !== undefined) this._far = projection.far
106
- this._projSet = true
107
- this._applyProjection()
108
- return this
109
- }
110
-
111
- /** Hosts that predate the call keep the fixed defaults (older wasm / native builds). */
112
- private _applyProjection(): void {
113
- if (this._sceneId < 0 || typeof _creator.setCameraProjection !== "function") return
114
- _creator.setCameraProjection(this._sceneId, this._fov, this._near, this._far)
115
- }
116
- private _following: Node | null = null
117
- /** The node the camera currently follows (`follow()`), or null. */
118
- get following(): Node | null { return this._following }
119
-
120
- /**
121
- * Make the camera ride a node: every late frame the camera takes the node's WORLD pose (−Z =
122
- * view direction), so movement aspects / animation on that node are camera moves — dolly shots,
123
- * a cutscene path, a `CameraPlace` in a scene file. A node carrying a `CameraPlace` also hands
124
- * over its projection (fov / near / far). `follow(null)` releases the camera where it is.
125
- * One camera per scene: following a new node stops following the previous one.
126
- */
127
- follow(target: Node | { node: Node, applyTo(camera: Camera): void } | null): this {
128
- const node = target === null ? null : target instanceof Node ? target : target.node
129
- if (this._following && this._following !== node) this._following.removeAspect(CameraFollowRig)
130
- this._following = node
131
- if (!node) return this
132
- // a CameraPlace on the node (or passed directly) sets the lens too
133
- const place = target instanceof Node
134
- ? (node as unknown as { cameraPlace?: { applyTo(camera: Camera): void } }).cameraPlace
135
- : target as { applyTo(camera: Camera): void }
136
- place?.applyTo(this)
137
- node.aspect(CameraFollowRig, { _camera: this })
138
- this.position = node.worldPosition
139
- this.quaternion = node.worldQuaternion
140
- return this
141
- }
142
-
143
- get displaySize(): [number, number] {
144
- const s = _creator.getDisplaySize()
145
- return [ s[0], s[1] ]
146
- }
147
-
148
- /** World-space ray direction through a screen pixel. */
149
- getViewDirection(screenX: number, screenY: number): Vec3 {
150
- if (this._sceneId < 0) throw new Error("Camera is not attached to a scene")
151
- const v = new Float32Array(3)
152
- _creator.getCameraViewDirection(this._sceneId, screenX, screenY, v)
153
- return new Vec3(v[0], v[1], v[2])
154
- }
155
-
156
- /** A pick ray from the camera through a screen pixel. */
157
- getRay(screenX: number, screenY: number): Ray {
158
- return new Ray(this.worldPosition, this.getViewDirection(screenX, screenY))
159
- }
160
- }
@@ -1,52 +0,0 @@
1
- // CameraPlace: "the camera as a place". A scene has ONE camera (`scene.camera`); a CameraPlace marks
2
- // a node as somewhere that camera can be — with the lens it uses there. Nothing is created engine-
3
- // side (no second camera, no ABI): it is a pose + a projection record on any node: an empty, an
4
- // empty mounted on a bone (`mount:`), a node a path aspect moves (a dolly shot), a model node.
5
- //
6
- // • In a scene FILE that runs (`open()` / `load()`), the `active: true` place drives
7
- // `scene.camera` (exactly `scene.camera.follow(node)`); exactly one per file.
8
- // • Inside a prefab or an `instantiate()`d subtree a place is inert DATA — the hosting scene
9
- // owns its camera. The host reads it (`inst.nodes.eye.get(CameraPlace)` → world pose + fov)
10
- // or anchors on it (`inst.alignTo(inst.nodes.eye)`).
11
- // • From code: `scene.camera.follow(dolly.get(CameraPlace))` — a cutscene shot; `follow(null)`
12
- // releases.
13
- // • Edit mode: draws an anchored frustum gizmo (the editor's camera preview reads the same place).
14
- // See docs/scene-camera-place-plan.md.
15
-
16
- import { Aspect } from "../core/Aspect"
17
- import type { FieldMeta } from "../core/fields"
18
- import { Gizmos } from "../scene/gizmos"
19
- import type { Camera } from "./Camera"
20
- import type { Node } from "./Node"
21
-
22
- export class CameraPlace extends Aspect<"cameraPlace", Node> {
23
- static readonly aspect = "cameraPlace"
24
- /** Vertical field of view in degrees (default 60) — smaller is a longer lens. */
25
- fov = 60
26
- /** Near clip distance. */
27
- near = 0.01
28
- /** Far clip distance = view range. */
29
- far = 1000
30
- /** The place that drives `scene.camera` when the file declaring it RUNS. Exactly one per file;
31
- * ignored inside prefabs / instantiated subtrees (the host scene owns its camera). */
32
- active = false
33
- /** Aspect ratio of the frustum gizmo only (the real aspect is the viewport's). */
34
- static fields: FieldMeta<CameraPlace> = {
35
- fov: { label: "FOV", min: 1, max: 179, step: 1 },
36
- near: { min: 0.001, max: 10, step: 0.01 },
37
- far: { min: 1, max: 100000, step: 1 },
38
- active: { label: "Active camera" },
39
- }
40
- static editor = { rebuild: true }
41
-
42
- /** Copy this place's projection onto a camera (`camera.follow(place)` does it for you). */
43
- applyTo(camera: Camera): void {
44
- camera.setProjection({ fov: this.fov, near: this.near, far: this.far })
45
- }
46
-
47
- /** Edit mode: the frustum marker (−Z = view), anchored on the node so it follows drags live and
48
- * clicking it selects the node. Play mode never calls this. */
49
- rebuild(): void {
50
- Gizmos.frustum(this.fov, { node: this.node, color: this.active ? "#ffffff" : "#9aa3b2" })
51
- }
52
- }
@@ -1,238 +0,0 @@
1
- // A character controller, as an aspect on a 3D Node, backed by Jolt's CharacterVirtual — a kinematic
2
- // collide-and-slide controller (NOT a rigid body: no bouncing/tipping, crisp control, built-in stair
3
- // stepping + slope handling). Requires a Shape (a capsule is recommended) and does NOT use Physics.
4
- //
5
- // const hero = new Mesh(capsuleGeometry(), material)
6
- // .aspect(Shape, { capsule: { halfHeight: 0.6, radius: 0.3 } })
7
- // .aspect(CharacterController)
8
- // setLoop(() => hero.controller.move((Input.key('KeyD') ? 1 : 0) - (Input.key('KeyA') ? 1 : 0) * SPEED,
9
- // (Input.key('KeyS') ? 1 : 0) - (Input.key('KeyW') ? 1 : 0) * SPEED))
10
- // Input.on('keydown', e => { if (e.code === 'Space' && hero.controller.grounded) hero.controller.velocityY = 7 })
11
- //
12
- // The two halves of the velocity have different NATURES, so they have different channels:
13
- //
14
- // • HORIZONTAL — `move(x, z)`, world units/s, a PER-FRAME COMMAND. A walking character's horizontal
15
- // velocity is muscle-driven and not conserved: stop pushing, stop moving. So the command expires
16
- // each frame — no call this frame means standing still, and releasing the keys needs no explicit
17
- // zero. It is latched for the whole frame, so every physics sub-step of that frame sees it.
18
- // • VERTICAL — `velocityY`, LATCHED ballistic state. Gravity integrates into it in the engine;
19
- // you seed it for a jump / dash / bounce pad / explosion (`velocityY = 7`, `velocityY += 3`).
20
- // There is deliberately no ground check: coyote time, double jumps and wall jumps all need
21
- // the caller's own condition.
22
- //
23
- // `velocity` reads back what the solver ENDED UP with after the last step (post-collision) — walk into
24
- // a wall and it reads ~0 even though you commanded 5. Everything runs on the engine's fixed clock, so
25
- // the aspect has no per-frame update: a character costs zero JS work per frame.
26
- //
27
- // FREE MODE — `gravityScale = 0` turns the character into a swimmer / flyer / drone: no gravity, and
28
- // (because Jolt would otherwise snap it down to the floor it passes over) no stick-to-floor and no
29
- // stair walking either. There the vertical is muscle-driven too, so `move()` takes all three
30
- // components and the whole velocity becomes a per-frame command.
31
- //
32
- // ROTATION AND SCALE stay the node's: the engine writes only the position, so a rig can be yawed and
33
- // scaled directly instead of needing a child node for the visuals (a capsule is symmetric about its up
34
- // axis, so its yaw is physically irrelevant either way).
35
- //
36
- // CROUCHING goes through the Shape aspect (`node.aspect(Shape, { capsule: … })`) — see `resizing`.
37
- //
38
- // Note: it collides with solid bodies but PASSES THROUGH triggers, and (v1) is not itself detected by
39
- // triggers and isn't pointer-pickable while active.
40
-
41
- import { Aspect } from "../core/Aspect"
42
- import type { FieldMeta } from "../core/fields"
43
- import { Vec3, cx, cy, cz, type Vec2Like, type Vec3Like } from "../math/vec"
44
- import type { CompAxis, CompWriter } from "../core/compWrite"
45
- import { Shape } from "./Shape"
46
- import type { Node } from "./Node"
47
-
48
- /** Where the character's feet are, as reported by the solver after the last step. */
49
- export type GroundState = "ground" | "slope" | "unsupported" | "air"
50
-
51
- // Index = JPH::EGroundState (0 OnGround / 1 OnSteepGround / 2 NotSupported / 3 InAir).
52
- const GROUND_STATES: readonly GroundState[] = ["ground", "slope", "unsupported", "air"]
53
-
54
- const scratch = new Float32Array(3)
55
- let warnedFreeMove = false
56
-
57
- export class CharacterController extends Aspect<"controller", Node> implements CompWriter {
58
- static readonly aspect = "controller"
59
-
60
- // Both tunables are accessor-backed (they write through to the engine), and describeFields only
61
- // enumerates own enumerable fields — so the inspector is told about them explicitly. The default
62
- // VALUES still come from a fresh instance through the getters; this only supplies the keys.
63
- static fields: FieldMeta<CharacterController> = {
64
- gravityScale: { editor: "number", min: 0, step: 0.1, label: "Gravity scale" },
65
- maxSlope: { editor: "number", min: 0, max: 89, step: 1, label: "Max slope°" },
66
- }
67
-
68
- private _charId = 0
69
- private _gravityScale = 2
70
- private _maxSlope = 45
71
-
72
- onAttach(): void {
73
- if (!_creator.physicsHasSupport || !_creator.physicsHasSupport()) return
74
- const shape = this.node.get(Shape)
75
- if (!shape) {
76
- throw new Error("CharacterController requires a Shape aspect (a capsule is recommended) — add it first: node.aspect(Shape, { capsule: {…} }).aspect(CharacterController)")
77
- }
78
- if (shape._isTriangleMesh) {
79
- throw new Error("CharacterController needs a convex Shape (capsule recommended) — { mesh: true } is a triangle mesh; use { mesh: 'convex' } or a capsule")
80
- }
81
- const shapeId = shape._claim() // the character owns the shape; it is not a rigid body
82
- // Read the options through their PUBLIC accessors, never the `_backing` fields: chisel drops an
83
- // accessor pair whose name is only ever an object-literal key, and then
84
- // `aspect(CharacterController, { gravityScale: 0 })` silently leaves the default in place (the
85
- // character keeps falling instead of entering free mode). One property read here keeps the pair.
86
- this._charId = _creator.characterCreate(this.node.id, shapeId, this.maxSlope)
87
- if (!this._charId) return
88
- _creator.characterSetGravityScale(this._charId, this.gravityScale)
89
- this.node._xf = this._charId // routes node.position / .x / .y / .z / .matrix writes to the character
90
- this.node._xfKind = 1
91
- }
92
-
93
- onDetach(): void {
94
- if (this._charId) { _creator.characterDestroy(this._charId); this._charId = 0 }
95
- this.node._xf = 0
96
- this.node._xfKind = 0
97
- this.node.get(Shape)?._recreatePickBody() // hands the routing back to the pick body
98
- }
99
-
100
- /** Native character id (0 until attached / no physics support). */
101
- get id(): number { return this._charId }
102
-
103
- /**
104
- * This frame's movement command, in world units/s — NOT normalized, NOT a per-frame displacement
105
- * (that is what Unity's `Move` takes; passing `v * dt` here gives a character 60× too slow).
106
- *
107
- * Two components = horizontal `(x, z)`, the everyday call. Three = the whole velocity, for free mode
108
- * (`gravityScale = 0`); with gravity on, the vertical component fights the ballistic one and the
109
- * character barely falls, so that combination warns.
110
- *
111
- * Sticky only within the frame: the command expires once the engine consumes it. It also **takes the
112
- * axis back from a latched `velocity`** — commanding is claiming ownership, which is what keeps the
113
- * two horizontal sources from ever fighting.
114
- */
115
- move(x: number, z: number): void
116
- move(v: Vec2Like | Vec3Like): void
117
- move(x: number | Vec2Like | Vec3Like, z?: number): void {
118
- if (!this._charId) return
119
- if (typeof x === "number") { _creator.characterMove(this._charId, x, z ?? 0); return }
120
- // cz() is typed for 3-component inputs; a Vec2 legitimately has no `z` and reads undefined here.
121
- const third = cz(x as Vec3Like) // undefined for a Vec2 / 2-tuple / 2-long typed array
122
- if (third === undefined) { _creator.characterMove(this._charId, cx(x), cy(x)); return }
123
- if (this._gravityScale !== 0 && !warnedFreeMove) {
124
- warnedFreeMove = true
125
- console.warn("CharacterController.move(): the 3-component form is the FREE-mode command (swimming / flying) and expects gravityScale = 0. With gravity on it overwrites the falling speed every frame, so the character will hang in the air. Use move(x, z) and velocityY for a walking character.")
126
- }
127
- _creator.characterMoveFree(this._charId, cx(x), cy(x), third)
128
- }
129
-
130
- /** Vertical velocity (world units/s) — LATCHED: gravity works on it, you seed it. `= 7` to jump,
131
- * `+= 3` to stack an explosion on top of the current motion. No ground check: guard it yourself
132
- * with `grounded` (or don't, for a double jump). Overridden every frame by a 3-component `move()`.
133
- * `velocity.y = 7` is the same channel — pick whichever reads better. */
134
- get velocityY(): number {
135
- if (!this._charId) return 0
136
- _creator.characterGetVelocity(this._charId, scratch)
137
- return scratch[1]
138
- }
139
- set velocityY(v: number) {
140
- if (this._charId) _creator.characterSetVerticalVelocity(this._charId, v)
141
- }
142
-
143
- /** Multiplier over the world gravity (`Physics.configure({ gravity })`); default 2, for the snappier
144
- * fall games want. **0 = free mode**: no gravity, no stick-to-floor, no stair stepping — a swimmer
145
- * or a drone, driven by the 3-component `move()`. */
146
- get gravityScale(): number { return this._gravityScale }
147
- set gravityScale(v: number) {
148
- this._gravityScale = v
149
- if (this._charId) _creator.characterSetGravityScale(this._charId, v)
150
- }
151
-
152
- /** Max ground slope (degrees) the character treats as walkable; default 45. Live. */
153
- get maxSlope(): number { return this._maxSlope }
154
- set maxSlope(v: number) {
155
- this._maxSlope = v
156
- if (this._charId) _creator.characterSetMaxSlope(this._charId, v)
157
- }
158
-
159
- /**
160
- * True while a requested collider resize hasn't taken — you asked to stand up and there is something
161
- * overhead. Resizing goes through the `Shape` aspect itself:
162
- *
163
- * hero.aspect(Shape, { capsule: CROUCHED }) // always fits — you are shrinking
164
- * hero.aspect(Shape, { capsule: STANDING }) // may be refused under a low ceiling
165
- * if (hero.controller.resizing) … // still crouched; call it again next frame
166
- *
167
- * A refusal changes nothing, so the retry is just the same call again — and it is an exact headroom
168
- * test against the real capsule, unlike a hand-rolled raycast (a ray is a line; a capsule has girth).
169
- * The FEET stay planted across a resize, so the character neither hovers nor sinks.
170
- */
171
- get resizing(): boolean {
172
- return this.node.get(Shape)?._resizeRefused ?? false
173
- }
174
-
175
- /** True while standing on walkable ground. */
176
- get grounded(): boolean {
177
- return this._charId ? _creator.characterGetGroundState(this._charId) === 0 : false
178
- }
179
-
180
- /** Where the feet are after the last step: on walkable ground, on too-steep ground, touching
181
- * something that can't support it, or in the air. */
182
- get groundState(): GroundState {
183
- return this._charId ? (GROUND_STATES[_creator.characterGetGroundState(this._charId)] ?? "air") : "air"
184
- }
185
-
186
- /**
187
- * READ — the velocity the solver ended up with after the most recent step (world units/s, fresh
188
- * Vec3): what HAPPENED, not what you asked for. Walking into a wall reads ~0, sliding along one
189
- * reads the tangent. Unlike Unity's synchronous `Move`, our step runs later in the frame, so a read
190
- * is up to one frame old — irrelevant for animation speed, fall damage or "am I blocked", which is
191
- * what it is for. (Exact wall contact would need a contact normal; the engine has none yet.)
192
- *
193
- * WRITE — LATCH the whole velocity: a knockback, a wall jump, a launch pad. Unlike `move()` it does
194
- * not expire, so the character keeps flying, and gravity still pulls the vertical down into a real
195
- * ballistic arc. It stays until `move()` takes the axis back — so a game simply doesn't call
196
- * `move()` while the throw lasts, and ends it on its own terms:
197
- *
198
- * hero.controller.velocity = [dir.x * 12, 6, dir.z * 12] // hit by the blast
199
- * // …in the loop:
200
- * if (thrown) { if (hero.controller.grounded) thrown = false } // landing ends it
201
- * else hero.controller.move(ix * SPEED, iz * SPEED) // …and this reclaims the axis
202
- *
203
- * Note nothing clears the latch by itself, landing included — so a thrown character that never gets
204
- * a `move()` keeps sliding along the ground (a kinematic controller has no friction).
205
- *
206
- * Reading is not the inverse of writing: `c.velocity = c.velocity` is NOT a no-op, because the read
207
- * reports the measured result (against a wall it is ~0 and would cancel the throw). In flight the
208
- * two agree closely, so read-modify-write mid-air behaves as expected.
209
- *
210
- * `c.velocity.y = 7` (the direct spelling) is a jump — the compiler routes it to the exact
211
- * `velocityY` channel via `_writeComp` below, no vector allocated — and `c.velocity.x = 3` latches
212
- * the whole vector with the measured velocity filling in the other two, i.e. the read-modify-write
213
- * above spelled naturally. A STORED copy is still a copy: `const v = c.velocity; v.y = 7` does nothing.
214
- */
215
- get velocity(): Vec3 {
216
- if (!this._charId) return new Vec3(0, 0, 0)
217
- _creator.characterGetVelocity(this._charId, scratch)
218
- return new Vec3(scratch[0], scratch[1], scratch[2])
219
- }
220
- set velocity(v: Vec3Like) {
221
- if (this._charId) _creator.characterSetVelocity(this._charId, cx(v), cy(v), cz(v))
222
- }
223
-
224
- /** Compile-time list (chisel reads it, then strips it): the getters whose `c.<getter>.<axis> = v`
225
- * spelling is routed to `_writeComp` below. See core/compWrite.ts. */
226
- static _comps = [ "velocity" ]
227
-
228
- /** @internal `c.velocity.<axis> = v` compiles to this: the vertical axis is the precise `velocityY`
229
- * channel, any other latches the whole vector with the measured rest. */
230
- _writeComp(_prop: string, axis: CompAxis, v: number): void {
231
- if (!this._charId) return
232
- if (axis === "y") { _creator.characterSetVerticalVelocity(this._charId, v); return }
233
- _creator.characterGetVelocity(this._charId, scratch)
234
- if (axis === "x") scratch[0] = v
235
- else if (axis === "z") scratch[2] = v
236
- _creator.characterSetVelocity(this._charId, scratch[0], scratch[1], scratch[2])
237
- }
238
- }