lecodes-cli 0.18.2 → 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 (224) hide show
  1. package/dist/index.js +1866 -301
  2. package/package.json +14 -12
  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/core/Aspect.ts +0 -512
  86. package/runtime/sdk/core/InspectorUI.ts +0 -212
  87. package/runtime/sdk/core/color.ts +0 -66
  88. package/runtime/sdk/core/compWrite.ts +0 -42
  89. package/runtime/sdk/core/events.ts +0 -38
  90. package/runtime/sdk/core/fields.ts +0 -120
  91. package/runtime/sdk/core/registry.ts +0 -23
  92. package/runtime/sdk/core/signals.ts +0 -277
  93. package/runtime/sdk/core/time.ts +0 -81
  94. package/runtime/sdk/g2/Camera2D.ts +0 -40
  95. package/runtime/sdk/g2/CharacterController2D.ts +0 -276
  96. package/runtime/sdk/g2/Node2D.ts +0 -267
  97. package/runtime/sdk/g2/OneWay2D.ts +0 -66
  98. package/runtime/sdk/g2/Physics2D.ts +0 -346
  99. package/runtime/sdk/g2/Scene2D.ts +0 -209
  100. package/runtime/sdk/g2/Shape2D.ts +0 -259
  101. package/runtime/sdk/g2/Sprite.ts +0 -89
  102. package/runtime/sdk/g2/SpriteAnimation.ts +0 -171
  103. package/runtime/sdk/g2/SpriteSheet.ts +0 -166
  104. package/runtime/sdk/g2/Texture2D.ts +0 -47
  105. package/runtime/sdk/g2/Tilemap.ts +0 -41
  106. package/runtime/sdk/g2/Tileset.ts +0 -71
  107. package/runtime/sdk/g2/Trigger2D.ts +0 -77
  108. package/runtime/sdk/g2/autotile.ts +0 -433
  109. package/runtime/sdk/g2/cells.ts +0 -91
  110. package/runtime/sdk/g2/defineScene2d.ts +0 -381
  111. package/runtime/sdk/g2/groups2d.ts +0 -106
  112. package/runtime/sdk/g2/loop.ts +0 -50
  113. package/runtime/sdk/g2/scenarios2d.ts +0 -69
  114. package/runtime/sdk/g2/touch.ts +0 -83
  115. package/runtime/sdk/gl/Camera.ts +0 -160
  116. package/runtime/sdk/gl/CameraPlace.ts +0 -52
  117. package/runtime/sdk/gl/CharacterController.ts +0 -238
  118. package/runtime/sdk/gl/Gearbox.ts +0 -212
  119. package/runtime/sdk/gl/Geometry.ts +0 -279
  120. package/runtime/sdk/gl/IK.ts +0 -193
  121. package/runtime/sdk/gl/InstancedMesh.ts +0 -132
  122. package/runtime/sdk/gl/Light.ts +0 -99
  123. package/runtime/sdk/gl/Lightmap.ts +0 -179
  124. package/runtime/sdk/gl/Material.ts +0 -245
  125. package/runtime/sdk/gl/Mesh.ts +0 -83
  126. package/runtime/sdk/gl/Model.ts +0 -64
  127. package/runtime/sdk/gl/Node.ts +0 -350
  128. package/runtime/sdk/gl/Noise.ts +0 -30
  129. package/runtime/sdk/gl/Particles.ts +0 -676
  130. package/runtime/sdk/gl/Physics.ts +0 -222
  131. package/runtime/sdk/gl/Plane.ts +0 -53
  132. package/runtime/sdk/gl/Ray.ts +0 -16
  133. package/runtime/sdk/gl/Scene.ts +0 -479
  134. package/runtime/sdk/gl/Shape.ts +0 -377
  135. package/runtime/sdk/gl/Texture.ts +0 -46
  136. package/runtime/sdk/gl/Trigger.ts +0 -45
  137. package/runtime/sdk/gl/Vehicle.ts +0 -473
  138. package/runtime/sdk/gl/Wheel.ts +0 -240
  139. package/runtime/sdk/gl/animation/AnimationClip.ts +0 -204
  140. package/runtime/sdk/gl/animation/Animator.ts +0 -87
  141. package/runtime/sdk/gl/animation/Layer.ts +0 -29
  142. package/runtime/sdk/gl/animation/Loop.ts +0 -25
  143. package/runtime/sdk/gl/animation/Playback.ts +0 -43
  144. package/runtime/sdk/gl/animation/core.ts +0 -294
  145. package/runtime/sdk/gl/controls.ts +0 -95
  146. package/runtime/sdk/gl/physicsEvents.ts +0 -20
  147. package/runtime/sdk/gl/scenarios.ts +0 -291
  148. package/runtime/sdk/gl/state.ts +0 -6
  149. package/runtime/sdk/gl/touch.ts +0 -68
  150. package/runtime/sdk/inject.ts +0 -186
  151. package/runtime/sdk/math/Mathf.ts +0 -118
  152. package/runtime/sdk/math/mat4.ts +0 -278
  153. package/runtime/sdk/math/quat.ts +0 -232
  154. package/runtime/sdk/math/vec.ts +0 -255
  155. package/runtime/sdk/plugins/camera.ts +0 -81
  156. package/runtime/sdk/plugins/geolocation.ts +0 -123
  157. package/runtime/sdk/plugins/oauth.ts +0 -61
  158. package/runtime/sdk/plugins/permission.ts +0 -7
  159. package/runtime/sdk/plugins/push.ts +0 -132
  160. package/runtime/sdk/plugins/qr.ts +0 -73
  161. package/runtime/sdk/plugins/service.ts +0 -47
  162. package/runtime/sdk/runtime/app.ts +0 -101
  163. package/runtime/sdk/runtime/appEvents.ts +0 -54
  164. package/runtime/sdk/runtime/channel.ts +0 -61
  165. package/runtime/sdk/runtime/clipboard.ts +0 -20
  166. package/runtime/sdk/runtime/datetime.ts +0 -329
  167. package/runtime/sdk/runtime/device.ts +0 -293
  168. package/runtime/sdk/runtime/fetch.ts +0 -77
  169. package/runtime/sdk/runtime/files.ts +0 -23
  170. package/runtime/sdk/runtime/input.ts +0 -175
  171. package/runtime/sdk/runtime/media.ts +0 -111
  172. package/runtime/sdk/runtime/misc.ts +0 -16
  173. package/runtime/sdk/runtime/net.ts +0 -36
  174. package/runtime/sdk/runtime/rpc.ts +0 -218
  175. package/runtime/sdk/runtime/service.ts +0 -83
  176. package/runtime/sdk/runtime/share.ts +0 -9
  177. package/runtime/sdk/runtime/storage.ts +0 -13
  178. package/runtime/sdk/runtime/touch.ts +0 -76
  179. package/runtime/sdk/scene/defineScene.ts +0 -1227
  180. package/runtime/sdk/scene/editorPlugins.ts +0 -92
  181. package/runtime/sdk/scene/gizmos.ts +0 -148
  182. package/runtime/sdk/scene/grammar.ts +0 -120
  183. package/runtime/sdk/scene/material.ts +0 -188
  184. package/runtime/sdk/server/auth/appConfig.ts +0 -12
  185. package/runtime/sdk/server/auth/global.ts +0 -80
  186. package/runtime/sdk/server/auth/host.ts +0 -318
  187. package/runtime/sdk/server/auth/models.ts +0 -83
  188. package/runtime/sdk/server/auth/types.ts +0 -50
  189. package/runtime/sdk/server/channel.ts +0 -56
  190. package/runtime/sdk/server/context.ts +0 -36
  191. package/runtime/sdk/server/db/defineDb.ts +0 -237
  192. package/runtime/sdk/server/db/fields.ts +0 -132
  193. package/runtime/sdk/server/db/httpTransport.ts +0 -93
  194. package/runtime/sdk/server/db/index.ts +0 -7
  195. package/runtime/sdk/server/db/marci/query.ts +0 -412
  196. package/runtime/sdk/server/db/types.ts +0 -202
  197. package/runtime/sdk/server/errors.ts +0 -12
  198. package/runtime/sdk/server/host.ts +0 -74
  199. package/runtime/sdk/server/inject.ts +0 -13
  200. package/runtime/sdk/server/runtime.ts +0 -133
  201. package/runtime/sdk/server/validate.ts +0 -87
  202. package/runtime/sdk/ui/NativeView.ts +0 -142
  203. package/runtime/sdk/ui/UI.ts +0 -39
  204. package/runtime/sdk/ui/UIBottomSheet.ts +0 -139
  205. package/runtime/sdk/ui/UIButton.ts +0 -101
  206. package/runtime/sdk/ui/UIContainer.ts +0 -60
  207. package/runtime/sdk/ui/UIImage.ts +0 -83
  208. package/runtime/sdk/ui/UIInput.ts +0 -185
  209. package/runtime/sdk/ui/UIModal.ts +0 -139
  210. package/runtime/sdk/ui/UINode.ts +0 -826
  211. package/runtime/sdk/ui/UIPager.ts +0 -362
  212. package/runtime/sdk/ui/UIPopover.ts +0 -100
  213. package/runtime/sdk/ui/UIScreen.ts +0 -123
  214. package/runtime/sdk/ui/UIScrollable.ts +0 -87
  215. package/runtime/sdk/ui/UISpacer.ts +0 -14
  216. package/runtime/sdk/ui/UITabs.ts +0 -236
  217. package/runtime/sdk/ui/UIText.ts +0 -51
  218. package/runtime/sdk/ui/UIVideo.ts +0 -88
  219. package/runtime/sdk/ui/UIVirtualizedList.ts +0 -241
  220. package/runtime/sdk/ui/UIWidget.ts +0 -127
  221. package/runtime/sdk/ui/fonts.ts +0 -13
  222. package/runtime/sdk/ui/presentable.ts +0 -117
  223. package/runtime/sdk/ui/router.ts +0 -132
  224. package/runtime/sdk/ui/theme.ts +0 -84
@@ -1,83 +0,0 @@
1
- // A renderable 3D node from raw geometry — the primitive shapes (box/sphere/cylinder/plane) and
2
- // Mesh.from for a custom Geometry. A GLB import is a different kind: see Model.load.
3
-
4
- import { cx, cy, cz, type Vec3Like } from "../math/vec"
5
- import {
6
- Geometry,
7
- type CylinderOptions,
8
- type PlaneOptions,
9
- type SphereOptions,
10
- } from "./Geometry"
11
- import { Material } from "./Material"
12
- import { Node } from "./Node"
13
-
14
- /** Common transform/render options every primitive factory accepts. */
15
- export type MeshOptions = {
16
- material?: Material
17
- position?: Vec3Like
18
- eulerAngles?: Vec3Like
19
- scale?: Vec3Like | number
20
- name?: string
21
- castShadows?: boolean
22
- receiveShadows?: boolean
23
- }
24
-
25
- export class Mesh extends Node {
26
- private _geometry?: Geometry
27
-
28
- constructor(geometry?: Geometry, material?: Material) {
29
- super()
30
- if (geometry) {
31
- const mat = material ?? Material.unlit()
32
- this._geometry = geometry
33
- this._materials = [ mat ]
34
- _creator.setMesh(this.id, Material.idOf(mat), geometry.vertices, geometry.normals, geometry.indices, geometry.uv, geometry._meshKind, geometry.uv1)
35
- }
36
- }
37
-
38
- override get geometry(): Geometry | undefined { return this._geometry }
39
-
40
- /** A Mesh always carries a material (slot 0) — see Node.setMaterial for the slot API. */
41
- override get material(): Material { return this._materials![0]! }
42
- override set material(m: Material) { this.setMaterial(m, 0) }
43
-
44
- set culling(v: boolean) { _creator.setCulling(this.id, v) }
45
- set castShadows(v: boolean) { _creator.setCastShadows(this.id, v) }
46
- set receiveShadows(v: boolean) { _creator.setReceiveShadows(this.id, v) }
47
-
48
- // --- factories ---
49
-
50
- static box(options: MeshOptions & { size?: Vec3Like | number } = {}): Mesh {
51
- // the size goes into the geometry (not a scale after): the lightmap chart is laid out per face area
52
- return apply(new Mesh(Geometry.box(options.size ?? 1), options.material), options)
53
- }
54
-
55
- static sphere(options: MeshOptions & SphereOptions = {}): Mesh {
56
- return apply(new Mesh(Geometry.sphere(options), options.material), options)
57
- }
58
-
59
- static cylinder(options: MeshOptions & CylinderOptions = {}): Mesh {
60
- return apply(new Mesh(Geometry.cylinder(options), options.material), options)
61
- }
62
-
63
- static plane(options: MeshOptions & PlaneOptions = {}): Mesh {
64
- return apply(new Mesh(Geometry.plane(options), options.material), options)
65
- }
66
-
67
- /** Wrap a custom Geometry. */
68
- static from(geometry: Geometry, options: MeshOptions = {}): Mesh {
69
- return apply(new Mesh(geometry, options.material), options)
70
- }
71
- }
72
-
73
- const sizeToVec = (s: Vec3Like | number): [number, number, number] => (typeof s === "number" ? [ s, s, s ] : [ cx(s), cy(s), cz(s) ])
74
-
75
- const apply = (mesh: Mesh, o: MeshOptions): Mesh => {
76
- if (o.position) mesh.position = o.position
77
- if (o.eulerAngles) mesh.eulerAngles = o.eulerAngles
78
- if (o.scale !== undefined) mesh.scale = typeof o.scale === "number" ? [ o.scale, o.scale, o.scale ] : o.scale
79
- if (o.name !== undefined) mesh.name = o.name
80
- if (o.castShadows !== undefined) mesh.castShadows = o.castShadows
81
- if (o.receiveShadows !== undefined) mesh.receiveShadows = o.receiveShadows
82
- return mesh
83
- }
@@ -1,64 +0,0 @@
1
- // A loaded GLB model — its own node kind (a GLB is a node hierarchy with baked animation clips),
2
- // distinct from Mesh (raw primitives, no animation). Animation is always present, reached as
3
- // model.anim (an Animator over the GLB's clips — crossfades, blend spaces, layers when you need them):
4
- // const hero = await Model.load(asset('./hero.glb'))
5
- // hero.anim.play('Run', { loop: true })
6
-
7
- import { fetch, type FetchResponse } from "../runtime/fetch"
8
- import { Node } from "./Node"
9
- import { Animator } from "./animation/Animator"
10
- import { Material } from "./Material"
11
-
12
- export class Model extends Node {
13
- /** The model's Animator — always present, its clip table = the GLB's embedded clips. Configure
14
- * more (external clips, blend spaces, layers) with `model.aspect(Animator, {...})`. */
15
- declare readonly anim: Animator
16
- /** @internal loaded through the lightmap material (docs/lightmap-plan.md); clones inherit it. */
17
- _lightmapped = false
18
-
19
- constructor(internalId: number) {
20
- super(internalId)
21
- this.aspect(Animator)
22
- }
23
-
24
- /** Duplicate this model — a deep copy of the GLB (meshes, skeleton, animation clips), attached to
25
- * the same parent and scene and sharing this model's current transform. The clone has its own
26
- * independent animation state (reach it via clone.anim). Mirrors Model.load's culling default. */
27
- clone(): Model {
28
- const id = _creator.cloneEntity(this.id)
29
- _creator.setGlbCulling(id, false)
30
- const m = new Model(id)
31
- m._lightmapped = this._lightmapped
32
- return m
33
- }
34
-
35
- /** Load a GLB model. Returns its root as a Model; play its baked clips via model.anim. */
36
- static load(
37
- source: string | FetchResponse,
38
- options: {
39
- culling?: boolean
40
- onProgress?: (p: { loaded: number, total?: number }) => void
41
- /** Load through the lightmap material so `Lightmap.load` can bind a baked atlas (the GLB needs
42
- * TEXCOORD_1 — `lecodes assets doctor --lightmap-uv`). Hosts without lightmap support ignore it. */
43
- lightmap?: boolean
44
- } = {},
45
- ): Promise<Model> {
46
- if (typeof source === "string") {
47
- return fetch(source, { useOnce: true, onProgress: options.onProgress }).then((resp) => {
48
- if (resp.status >= 400) return Promise.reject(new Error(`Failed to load GLB from ${source}. HTTP ${resp.status}`))
49
- return Model.load(resp, options)
50
- })
51
- }
52
- return new Promise<Model>((resolve, reject) => {
53
- const lightmapped = !!options.lightmap && !!_creator.setNextGlbLightmapped
54
- if (lightmapped) _creator.setNextGlbLightmapped!(Material.idOf(Material._lightmapTemplate()))
55
- _creator.createGlb((source as unknown as { _id: number })._id, (entityId: number) => {
56
- if (entityId === 0) { reject(new Error("Failed to load GLB")); return }
57
- const model = new Model(entityId)
58
- model._lightmapped = lightmapped
59
- if (options.culling !== true) _creator.setGlbCulling(entityId, false)
60
- resolve(model)
61
- }, reject)
62
- })
63
- }
64
- }
@@ -1,350 +0,0 @@
1
- // Base 3D scene node — an empty transform. Mesh and Light extend it, so every node operation works
2
- // on them. Native (creator-gl) owns the authoritative transform; reads decompose a cached local
3
- // matrix (re-synced lazily, since physics can write the transform from native each frame).
4
- //
5
- // Capabilities are aspects: node.aspect(Physics, …) (→ node.physics), node.aspect(Shape, …).
6
- // A GLB import is the Model node kind (with the Animator aspect at model.anim). Hierarchy lives here.
7
-
8
- import { AspectHost } from "../core/Aspect"
9
- import { Registry } from "../core/registry"
10
- import { Vec3, cx, cy, cz, type Vec3Like } from "../math/vec"
11
- import { Quat, cw, type QuatLike } from "../math/quat"
12
- import { Mat4, type Mat4Like } from "../math/mat4"
13
- import type { Geometry } from "./Geometry"
14
- import { registerTouchEndEvent, registerTouchStartEvent } from "./touch"
15
- import { ensurePhysicsEvents } from "./physicsEvents"
16
- import { Material } from "./Material"
17
- import type { CompAxis, CompWriter } from "../core/compWrite"
18
- import type { ClickEvent, TouchStartEvent } from "../runtime/touch"
19
-
20
- /** id → Node, so host callbacks (touch hits, animation events) route back to the owning object. */
21
- export const nodeRegistry = new Registry<Node>()
22
-
23
- export type NodeEvents = {
24
- click: (ev: ClickEvent<Node | null>) => void
25
- touchstart: (ev: TouchStartEvent<Node | null>) => void
26
- /** AR anchors only (`scene.root` / `scene.createAnchor()`): the anchor began tracking. */
27
- track: () => void
28
- /** AR anchors only: the anchor lost tracking. */
29
- untrack: () => void
30
- /** A physics contact/trigger overlap began (the other body's node). Needs a Shape + Physics/Trigger. */
31
- enter: (other: Node) => void
32
- /** A physics contact/trigger overlap ended. */
33
- exit: (other: Node) => void
34
- }
35
-
36
- const shortName = (s: string): string => s.slice(s.search(/[^:|]*$/))
37
-
38
- /** Fallback for hosts without `_creator.findNode` (exact name, then short name, first in tree order —
39
- * no skin-joint preference: that needs the host's skin knowledge). 0 when absent. */
40
- const findNodeByWalk = (rootId: number, name: string): number => {
41
- let exact = 0
42
- let loose = 0
43
- const want = shortName(name)
44
- _creator.traverse(rootId, (id) => {
45
- if (exact) return
46
- const n = _creator.getName(id)
47
- if (n === name) exact = id
48
- else if (!loose && shortName(n) === want) loose = id
49
- })
50
- return exact || loose
51
- }
52
-
53
- // Scratch for the world-position read-back in _setOwnedPosition (one per module, never handed out).
54
- const worldScratch = new Float32Array(3)
55
-
56
- export class Node extends AspectHost<NodeEvents> implements CompWriter {
57
- /** Native entity handle. */
58
- readonly id: number
59
-
60
- /** AR anchors only — whether the anchor is currently being tracked. Kept in step with the
61
- * 'track' / 'untrack' events; `false` for any non-anchor node. */
62
- isTracked = false
63
-
64
- private _matrix?: Float32Array
65
- private _worldMatrix?: Float32Array
66
- private _boneCache?: Map<string, Node | null>
67
- /** Materials assigned through setMaterial, by primitive slot (a Mesh fills slot 0 itself). */
68
- protected _materials?: Material[]
69
-
70
- constructor(internalId?: number) {
71
- super()
72
- this.id = internalId ?? _creator.createEntity()
73
- nodeRegistry.set(this.id, this)
74
- }
75
-
76
- /** Geometry, if this node is a Mesh (used by physics auto-shape). Overridden by Mesh. */
77
- get geometry(): Geometry | undefined { return undefined }
78
-
79
- // --- name / visibility ---
80
- get name(): string { return _creator.getName(this.id) }
81
- set name(value: string) { _creator.setName(this.id, value) }
82
- get visible(): boolean { return _creator.isVisible(this.id) }
83
- set visible(value: boolean) { _creator.setVisible(this.id, value) }
84
-
85
- // --- local transform ---
86
- // Native owns the authoritative matrix. `_sync()` reads it into a per-node scratch buffer on EVERY
87
- // call: a bridge read is ~40 ns on QuickJS — what the Date.now() of a staleness check cost — so a
88
- // cache bought nothing and needed invalidation hooks (physics / controllers / animation rewrite
89
- // transforms natively between frames). The PUBLIC `matrix` getter hands back a fresh Mat4 *copy*
90
- // (value semantics — mutating it never touches the node until you assign it back); `_sync()` is
91
- // the internal hot path for component reads.
92
- private _sync(): Float32Array {
93
- const m = this._matrix ?? (this._matrix = new Float32Array(16))
94
- _creator.getMatrix(this.id, m)
95
- return m
96
- }
97
-
98
- get matrix(): Mat4 { return new Mat4(this._sync()) }
99
- set matrix(m: Mat4Like) {
100
- const buf = this._matrix ?? (this._matrix = new Float32Array(16))
101
- buf.set(matArr(m))
102
- _creator.setMatrix(this.id, buf)
103
- // A matrix write on a physics-owned node must move the engine object too, or it is silently
104
- // overwritten by the next sync. Position always; rotation only for a BODY (see _setOwnedRotation).
105
- // Scale is never routed — a Jolt shape is built at a fixed size, that is `Shape`'s job.
106
- // `worldMatrix` composes into this setter, so it is covered here too.
107
- if (this._xf) {
108
- this._setOwnedPosition(buf[12], buf[13], buf[14])
109
- this._setOwnedRotation()
110
- }
111
- }
112
-
113
- get worldMatrix(): Mat4 {
114
- if (this.parent === null) return this.matrix
115
- if (!this._worldMatrix) this._worldMatrix = new Float32Array(16)
116
- _creator.getWorldMatrix(this.id, this._worldMatrix)
117
- return new Mat4(this._worldMatrix)
118
- }
119
- set worldMatrix(m: Mat4Like) {
120
- const p = this.parent
121
- if (p === null) { this.matrix = m; return }
122
- // local = parentWorld⁻¹ · world, composed here: the native setWorldMatrix ignores the parent (it
123
- // wrote the world matrix as the local one — a bone under a rotated Model wrapper read back with the
124
- // ancestors' rotation applied twice)
125
- this.matrix = p.worldMatrix.invert().mul(m)
126
- }
127
-
128
- /** @internal The engine object that mirrors this node's transform, if any — a physics body or a
129
- * character (0 = none). `position` writes are routed to it as well, because the engine is the
130
- * authority: on a dynamic body a plain setPosition is overwritten by the next sync, and on a
131
- * static / pick / trigger body it would leave the COLLIDER behind while the node moved. Set by
132
- * Shape (bodies) and CharacterController. */
133
- _xf = 0
134
- /** @internal What `_xf` is: 1 = character, 2 = physics body. */
135
- _xfKind = 0
136
-
137
- // Route a position write to the engine object that owns this node. Kept out of the setters' hot path
138
- // (they only pay one truthiness check). `position` stays a LOCAL coordinate like on every node, while
139
- // physics works in world space — so the node is written first and the world point read back natively
140
- // (one bridge call for any parent depth, no allocation; the native transform manager updates the
141
- // world matrix synchronously).
142
- private _setOwnedPosition(x: number, y: number, z: number): void {
143
- _creator.setPosition(this.id, x, y, z) // keep the node itself right, so a read-back is immediate
144
- _creator.getWorldPosition(this.id, worldScratch)
145
- if (this._xfKind === 1) _creator.characterSetPosition(this._xf, worldScratch[0], worldScratch[1], worldScratch[2])
146
- else _creator.physicsSetBodyPosition(this._xf, worldScratch[0], worldScratch[1], worldScratch[2])
147
- }
148
-
149
- // The rotation half of the same routing. BODIES ONLY: a character's rotation is node-owned by design
150
- // (the engine writes its position and nothing else — a capsule is symmetric about its up axis, so its
151
- // yaw is physically meaningless), and pushing one into Jolt would invent an authority that isn't there.
152
- // The node has already been written by the caller, so the world rotation is read back from it — that
153
- // way the engine's own euler convention decides, instead of this file re-deriving it.
154
- private _setOwnedRotation(): void {
155
- if (this._xfKind !== 2 || !_creator.physicsSetBodyRotation) return
156
- const q = this.worldMatrix.rotation
157
- _creator.physicsSetBodyRotation(this._xf, q.x, q.y, q.z, q.w)
158
- }
159
-
160
- get position(): Vec3 { const m = this._sync(); return new Vec3(m[12], m[13], m[14]) }
161
- set position(v: Vec3Like) {
162
- if (this._xf) this._setOwnedPosition(cx(v), cy(v), cz(v))
163
- else _creator.setPosition(this.id, cx(v), cy(v), cz(v))
164
- }
165
-
166
- // Scalar position accessors — one axis without touching the others (`node.x = 3`). The direct spelling
167
- // `node.position.x = 3` compiles to the same thing (chisel's comp_write pass routes it through
168
- // `_writeComp` below); only a STORED copy of `position` is still a copy.
169
- get x(): number { return this._sync()[12] }
170
- set x(v: number) { const m = this._sync(); if (this._xf) this._setOwnedPosition(v, m[13], m[14]); else _creator.setPosition(this.id, v, m[13], m[14]) }
171
- get y(): number { return this._sync()[13] }
172
- set y(v: number) { const m = this._sync(); if (this._xf) this._setOwnedPosition(m[12], v, m[14]); else _creator.setPosition(this.id, m[12], v, m[14]) }
173
- get z(): number { return this._sync()[14] }
174
- set z(v: number) { const m = this._sync(); if (this._xf) this._setOwnedPosition(m[12], m[13], v); else _creator.setPosition(this.id, m[12], m[13], v) }
175
-
176
- /** @internal chisel `comp_write` (see core/compWrite.ts): `node.position.<axis> = v` is compiled to a
177
- * call here; the scalar setters already carry the physics routing. The list is compile-time data. */
178
- static _comps = [ "position" ]
179
- _writeComp(_prop: string, axis: CompAxis, v: number): void {
180
- if (axis === "x") this.x = v
181
- else if (axis === "y") this.y = v
182
- else if (axis === "z") this.z = v
183
- }
184
-
185
- get scale(): Vec3 {
186
- const m = this._sync()
187
- return new Vec3(Math.hypot(m[0], m[1], m[2]), Math.hypot(m[4], m[5], m[6]), Math.hypot(m[8], m[9], m[10]))
188
- }
189
- set scale(v: Vec3Like | number) {
190
- if (typeof v === "number") _creator.setScale(this.id, v, v, v)
191
- else _creator.setScale(this.id, cx(v), cy(v), cz(v))
192
- }
193
-
194
- get quaternion(): Quat { return new Mat4(this._sync()).rotation }
195
- set quaternion(v: QuatLike) {
196
- _creator.setQuaternion(this.id, cx(v), cy(v), cz(v), cw(v))
197
- if (this._xf) this._setOwnedRotation()
198
- }
199
-
200
- get eulerAngles(): Vec3 { return new Mat4(this._sync()).eulerAngles }
201
- // order 1 = YXZ — the SDK's euler convention (math/quat.ts); the getter also extracts YXZ, so
202
- // the pair round-trips. (Historically this passed 0/XYZ AND the engine stored the euler matrix
203
- // transposed — the setter applied the INVERSE rotation. Both fixed 2026-07-10.)
204
- set eulerAngles(v: Vec3Like) {
205
- _creator.setEulerAngles(this.id, cx(v), cy(v), cz(v), 1)
206
- if (this._xf) this._setOwnedRotation()
207
- }
208
-
209
- // --- world-space reads ---
210
- get forward(): Vec3 {
211
- const v = new Float32Array(3)
212
- _creator.getWorldDirection(this.id, 2, v)
213
- return new Vec3(-v[0], -v[1], -v[2])
214
- }
215
- get worldPosition(): Vec3 {
216
- const v = new Float32Array(3)
217
- _creator.getWorldPosition(this.id, v)
218
- return new Vec3(v[0], v[1], v[2])
219
- }
220
- get worldScale(): Vec3 { return this.worldMatrix.scaling }
221
- get worldQuaternion(): Quat { return this.worldMatrix.rotation }
222
- get worldEulerAngles(): Vec3 { return this.worldMatrix.eulerAngles }
223
-
224
- // --- hierarchy ---
225
- get parent(): Node | null {
226
- const id = _creator.getParent(this.id)
227
- return id === 0 ? null : (nodeRegistry.get(id) ?? new Node(id))
228
- }
229
- get children(): Node[] {
230
- return _creator.getChildren(this.id).map((id) => nodeRegistry.get(id) ?? new Node(id))
231
- }
232
- get childCount(): number { return _creator.getChildCount(this.id) }
233
- getChild(index: number): Node | null {
234
- const id = _creator.getChild(this.id, index)
235
- return id === 0 ? null : (nodeRegistry.get(id) ?? new Node(id))
236
- }
237
- /** Parent the given nodes under this one. */
238
- add(...children: Node[]): this {
239
- for (const c of children) _creator.setParent(c.id, this.id, false)
240
- return this
241
- }
242
- setParent(parent: Node | null, worldPositionStays = false): this {
243
- if (parent === null) _creator.setParentNull(this.id, worldPositionStays)
244
- else _creator.setParent(this.id, parent.id, worldPositionStays)
245
- return this
246
- }
247
- traverse(callback: (node: Node) => void): void {
248
- callback(this)
249
- _creator.traverse(this.id, (id) => callback(nodeRegistry.get(id) ?? new Node(id)))
250
- }
251
- // --- materials (any renderable: a Mesh, or an internal node of a loaded Model) ---
252
- /** The material assigned to slot 0 through this API (null before one is set — a GLB part's own
253
- * glTF material stays native-side). */
254
- get material(): Material | null { return this.getMaterial(0) }
255
- set material(m: Material) { this.setMaterial(m, 0) }
256
- /** Replace the material of one primitive slot (`index` = the primitive's order in the glTF
257
- * mesh; a primitive-shape Mesh has one slot). */
258
- setMaterial(material: Material, index = 0): this {
259
- (this._materials ??= [])[index] = material
260
- _creator.setMaterial(this.id, Material.idOf(material), index)
261
- return this
262
- }
263
- /** The material previously assigned to `index` (null when none was — see Mesh for the probe). */
264
- getMaterial(index = 0): Material | null {
265
- return this._materials?.[index] ?? null
266
- }
267
-
268
- /** Find a descendant by name — bones of a loaded Model included (`hero.bone('RightHand').add(sword)`).
269
- * Same rule the Animator binds clips with: exact name first, then the part after the last `:` / `|`
270
- * (Mixamo `mixamorig:Hips` matches `Hips`); a skinned joint beats a plain node of the same name.
271
- * Cached per name; null if absent. */
272
- bone(name: string): Node | null {
273
- const cache = (this._boneCache ??= new Map<string, Node | null>())
274
- const hit = cache.get(name)
275
- if (hit !== undefined) return hit
276
- const id = _creator.findNode ? _creator.findNode(this.id, name) : findNodeByWalk(this.id, name)
277
- const found = id ? nodeRegistry.get(id) ?? new Node(id) : null
278
- cache.set(name, found)
279
- return found
280
- }
281
-
282
- // --- behaviors are aspects: node.aspect(Shape, …) / node.aspect(Physics, …) ---
283
-
284
- /** Orient the node so the given axis (default "-z", "forward") points at a world point. */
285
- lookAt(point: Vec3Like, mode: "z" | "-z" | "x" | "-x" | "y" | "-y" = "-z", ortho: Vec3Like = [ 0, 1, 0 ]): this {
286
- const world = this.worldMatrix
287
- const pos = world.position
288
- let dir = new Vec3(point).sub(pos)
289
- const len = dir.length()
290
- if (len === 0) return this
291
- let axis: string = mode
292
- if (mode.startsWith("-")) { dir = dir.scale(-1 / len); axis = mode.slice(1) }
293
- else dir = dir.scale(1 / len)
294
-
295
- const o = new Vec3(ortho)
296
- let forward: Vec3, right: Vec3, up: Vec3
297
- if (axis === "x") {
298
- right = dir; up = o.cross(right).normalize(); forward = up.cross(right).normalize()
299
- } else if (axis === "y") {
300
- up = dir; right = o.cross(up).normalize(); forward = up.cross(right).normalize()
301
- } else {
302
- forward = dir; right = o.cross(dir).normalize(); up = dir.cross(right)
303
- }
304
- const s = world.scaling, w = world.m
305
- this.worldMatrix = Float32Array.of(
306
- right.x * s.x, right.y * s.x, right.z * s.x, 0,
307
- up.x * s.y, up.y * s.y, up.z * s.y, 0,
308
- forward.x * s.z, forward.y * s.z, forward.z * s.z, 0,
309
- w[12], w[13], w[14], w[15],
310
- )
311
- return this
312
- }
313
-
314
- // --- events ---
315
- override addEventListener<K extends keyof NodeEvents>(channel: K, callback: NodeEvents[K]): void {
316
- if (channel === "click") registerTouchEndEvent()
317
- else if (channel === "touchstart") registerTouchStartEvent()
318
- else if (channel === "enter" || channel === "exit") ensurePhysicsEvents()
319
- super.addEventListener(channel, callback)
320
- }
321
-
322
- /** @internal — called by the touch system to deliver a hit. */
323
- _dispatchEvent<K extends keyof NodeEvents>(channel: K, ...args: Parameters<NodeEvents[K]>): void {
324
- this.dispatch(channel, ...args)
325
- }
326
-
327
- /** @internal — emit a physics contact/trigger event (the physics-event router can't reach dispatch). */
328
- _emitCollision(channel: "enter" | "exit", other: Node): void { this.dispatch(channel, other) }
329
-
330
-
331
- /** Destroy this node and its whole subtree. Every aspect in the subtree is detached first (its
332
- * `onDetach` runs: physics bodies released, updaters unregistered, listeners dropped) — native
333
- * `destroyEntity` frees the entity tree but knows nothing about JS-side aspects, and a destroyed
334
- * node's `update()` must not keep ticking. Deepest nodes go first, then this one. */
335
- destroy(): void {
336
- const subtree: Node[] = [this]
337
- _creator.traverse(this.id, (id) => { const n = nodeRegistry.get(id); if (n) subtree.push(n) })
338
- for (let i = subtree.length - 1; i >= 0; i--) {
339
- const n = subtree[i]
340
- n._detachAll()
341
- n._xf = 0
342
- n._xfKind = 0
343
- nodeRegistry.delete(n.id)
344
- }
345
- _creator.destroyEntity(this.id)
346
- }
347
- }
348
-
349
- /** Read the raw column-major elements out of a Mat4 wrapper or a plain length-16 array. */
350
- const matArr = (m: Mat4Like): ArrayLike<number> => (m as { m?: ArrayLike<number> }).m ?? (m as ArrayLike<number>)
@@ -1,30 +0,0 @@
1
- // Native FastNoise (Perlin/fractal) sampler. Ported from worker/src/utils/PerlinNoise.ts.
2
-
3
- export class Noise {
4
- private readonly _id: number
5
-
6
- constructor() {
7
- this._id = _creator.createNoise()
8
- }
9
-
10
- /** Sample 2D noise. */
11
- get(x: number, y: number): number { return _creator.getNoise2D(this._id, x, y) }
12
- /** Sample 3D noise. */
13
- get3d(x: number, y: number, z: number): number { return _creator.getNoise3D(this._id, x, y, z) }
14
-
15
- private _frequency = 0.01
16
- get frequency(): number { return this._frequency }
17
- set frequency(v: number) { this._frequency = v; _creator.setNoiseFrequency(this._id, v) }
18
-
19
- private _octaves = 1
20
- get octaves(): number { return this._octaves }
21
- set octaves(v: number) { this._octaves = v; _creator.setNoiseOctaves(this._id, v) }
22
-
23
- private _fractalLacunarity = 2
24
- get fractalLacunarity(): number { return this._fractalLacunarity }
25
- set fractalLacunarity(v: number) { this._fractalLacunarity = v; _creator.setNoiseFractalLunacrity(this._id, v) }
26
-
27
- private _fractalGain = 0.5
28
- get fractalGain(): number { return this._fractalGain }
29
- set fractalGain(v: number) { this._fractalGain = v; _creator.setNoiseFractalGain(this._id, v) }
30
- }