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,409 +0,0 @@
1
- // A collision shape, as an aspect on a 3D Node. PURE GEOMETRY: attaching a Shape does not by itself
2
- // make the node rigid or a sensor — pair it with a Physics (rigid body) or a Trigger (sensor), both
3
- // of which require a Shape. The Shape also makes the node pointer-pickable: on its own it creates a
4
- // lightweight "pick-only" JoltPhysics body (collides with nothing, exists only for the raycast), so
5
- // any node with a Shape can receive click/touchstart. Physics/Trigger REPLACE that body with the real
6
- // one. Set `raycast = false` to opt a shape out of pointer picking.
7
- //
8
- // wall.aspect(Shape, { box: [1, 2, 1] }).aspect(Physics, { motion: 'static' })
9
- // coin.aspect(Shape, { sphere: 0.5 }).aspect(Trigger)
10
- // crate.aspect(Shape, {}).aspect(Physics) // auto box from the mesh AABB × world scale
11
- // barrel.aspect(Shape, { capsule: {…}, origin: [0, 0.9, 0] }) // collider lifted off the pivot
12
- // tree.aspect(Shape, {}).shape.fit('capsule') // dimensions measured from the model
13
- //
14
- // Explicit dims are world-space half-extents (not scaled by the node). Use {} ("auto") to track the
15
- // mesh × world scale. `origin` moves the shape off the node's pivot (world units, in the node's
16
- // rotated frame) — the usual case is a model whose art sits above its origin. Note: a Shape-only
17
- // pick body is a STATIC snapshot at attach time — for a moving pickable object add a Physics body
18
- // (which follows the sim), rather than relying on the pick body.
19
-
20
- import { Aspect } from "../core/Aspect"
21
- import { cx, cy, cz, type Vec3Like } from "../math/vec"
22
- import type { Node } from "./Node"
23
-
24
- /** Which geometry a Shape uses — the one-of the fields below encode (see `_build`'s order). */
25
- export type ShapeKind = "auto" | "box" | "sphere" | "cylinder" | "capsule" | "mesh" | "convex" | "heightfield"
26
-
27
- /** What a terrain node carries for `Shape { heightfield: true }` (gl/Terrain.ts `_data`). */
28
- type TerrainData = { sizeX: number, sizeZ: number, cellSize: number, heights: Float32Array, holes: Uint8Array | null }
29
- const terrainOf = (node: Node): TerrainData | null => (node as unknown as { _terrain?: { _data: TerrainData } })._terrain?._data ?? null
30
-
31
- /** A node-local AABB, in the node's OWN frame (its own transform excluded), before world scale. */
32
- export type ShapeBounds = { min: [number, number, number], max: [number, number, number] }
33
-
34
- /** Dimensions measured from a node's rendered geometry — what `Shape.fit` writes, and the numbers
35
- * the scene editor's "Fit to mesh" button puts in the file. Exactly one dimension key is set (none
36
- * for a mesh/convex kind, whose geometry is the mesh itself); `origin` centres it on the art. */
37
- export type ShapeFit = {
38
- box?: [number, number, number]
39
- sphere?: number
40
- cylinder?: { halfHeight: number, radius: number }
41
- capsule?: { halfHeight: number, radius: number }
42
- origin: [number, number, number]
43
- }
44
-
45
- /** Half-extents below this collapse a shape (Jolt refuses a box thinner than its convex radius,
46
- * and a zero-radius sphere) — a fitted plane or a decal quad lands here. */
47
- const MIN_HALF = 0.001
48
- const r4 = (v: number): number => Math.round(v * 1e4) / 1e4
49
- const dim = (v: number): number => Math.max(MIN_HALF, r4(v))
50
-
51
- export class Shape extends Aspect<"shape", Node> {
52
- static readonly aspect = "shape"
53
-
54
- /** Box half-extents [hx, hy, hz] (world units). */
55
- box?: Vec3Like
56
- /** Sphere radius. */
57
- sphere?: number
58
- /** Cylinder aligned on Y: half-height + radius. */
59
- cylinder?: { halfHeight: number, radius: number }
60
- /** Capsule aligned on Y: half-height of the cylinder section + radius. */
61
- capsule?: { halfHeight: number, radius: number }
62
- /** The node's own triangles (a Mesh's geometry or a Model's GLB, world scale applied).
63
- * `true` = exact triangle mesh — static/kinematic bodies, triggers, characters' ground and picking
64
- * only (a dynamic body can't be a triangle mesh; Physics throws). `'convex'` = convex hull of the
65
- * vertices, works for dynamic bodies too. A Model uses its bind pose (skinned parts skipped). */
66
- mesh?: boolean | "convex"
67
- /** The node's terrain (`Terrain.create(...).node`) as a Jolt height field — the exact drawn grid,
68
- * holes included (docs/terrain-plan.md). Static / kinematic / picking / character ground only,
69
- * like `mesh: true`. `terrain.commit()` pushes edits into the live shape. */
70
- heightfield?: boolean
71
- /**
72
- * The shape's centre, relative to the node's origin — world units in the node's ROTATED frame
73
- * (the node's scale is ignored, exactly like the explicit dimensions above). Default [0, 0, 0].
74
- *
75
- * A model's pivot is rarely its centre of volume (a character stands ON its origin, a wheel hangs
76
- * off its axle), so a collider centred on the pivot is half sunk into the floor. `origin` lifts it
77
- * without moving the node: `{ capsule: { halfHeight: 0.6, radius: 0.3 }, origin: [0, 0.9, 0] }`.
78
- * `fit()` and the scene editor's "Fit to mesh" fill it in from the rendered bounds.
79
- *
80
- * On a DYNAMIC body the offset also moves the centre of mass, so a lopsided collider tips the way
81
- * you would expect. With no `origin` an auto shape (`{}`) still centres itself on the node's own
82
- * mesh bounds — only explicit dimensions sit on the pivot by default.
83
- */
84
- origin?: Vec3Like
85
- /** Whether pointer rays can hit this shape (click / touchstart). Default true. */
86
- raycast = true
87
-
88
- private _shapeId = 0
89
- private _bodyId = 0
90
- /** Centre the "auto" box measured for itself — used when `origin` isn't set explicitly. */
91
- private _autoOrigin: [number, number, number] | null = null
92
- /** Geometry props as of the last build (see onReconfigure). */
93
- private _sig = ""
94
-
95
- /** @internal native shape id (0 if the build has no physics). */
96
- get shapeId(): number { return this._shapeId }
97
- /** @internal current native body id (a pick-only body until Physics/Trigger claim it). */
98
- get bodyId(): number { return this._bodyId }
99
-
100
- onAttach(): void {
101
- if (!_creator.physicsHasSupport || !_creator.physicsHasSupport()) return
102
- this._shapeId = this._build()
103
- if (this._shapeId) this._createPickBody()
104
- }
105
-
106
- onDetach(): void {
107
- if (this._bodyId) { _creator.physicsRemoveBody(this._bodyId); this._bodyId = 0 }
108
- if (this._shapeId) { _creator.physicsDestroyShape(this._shapeId); this._shapeId = 0 }
109
- this.node._xf = 0
110
- this.node._xfKind = 0
111
- }
112
-
113
- /** Re-configuring (`node.aspect(Shape, { origin })`) rebuilds the geometry when a dimension, the
114
- * kind or the origin changed, and re-applies pickability. The body keeps its id and velocity. */
115
- onReconfigure(): void {
116
- if (this._signature() !== this._sig) this._rebuild()
117
- if (this._bodyId) _creator.physicsSetPickable(this._bodyId, this.raycast)
118
- }
119
-
120
- /**
121
- * Measure the node's rendered geometry and size the collider to it — the code half of the scene
122
- * editor's "Fit to mesh". Keeps the current kind unless one is passed; `auto` becomes an explicit
123
- * box, and a mesh/convex shape only has its `origin` cleared (its geometry IS the mesh).
124
- *
125
- * crate.aspect(Shape, {}).shape.fit() // box around what the model actually draws
126
- * hero.aspect(Shape, {}).shape.fit('capsule') // capsule of the same height + girth
127
- *
128
- * A model loads asynchronously, so fit AFTER its `load()` resolves — an empty subtree measures as
129
- * nothing and the call is a no-op (it warns). Rebuilds the live shape, keeping the body.
130
- */
131
- fit(kind?: ShapeKind): this {
132
- const f = Shape.fitTo(this.node, kind ?? this.kind)
133
- if (!f) {
134
- console.warn(`Shape.fit: "${this.node.name}" has no geometry to measure yet (a model still loading, or a node with no mesh)`)
135
- return this
136
- }
137
- this.box = f.box
138
- this.sphere = f.sphere
139
- this.cylinder = f.cylinder
140
- this.capsule = f.capsule
141
- if (kind === "mesh") this.mesh = true
142
- else if (kind === "convex") this.mesh = "convex"
143
- else if (kind !== undefined) this.mesh = undefined
144
- this.origin = f.origin
145
- this._rebuild()
146
- return this
147
- }
148
-
149
- /** Which geometry this shape currently uses (the `_build` dispatch, as a name). */
150
- get kind(): ShapeKind {
151
- if (this.heightfield) return "heightfield"
152
- if (this.mesh === "convex") return "convex"
153
- if (this.mesh) return "mesh"
154
- if (this.sphere !== undefined) return "sphere"
155
- if (this.cylinder) return "cylinder"
156
- if (this.capsule) return "capsule"
157
- if (this.box) return "box"
158
- return "auto"
159
- }
160
-
161
- // ---- fitting (static, so the scene-editor harness can measure without attaching) -------------
162
-
163
- /**
164
- * The node's subtree AABB in its OWN local frame (its own transform excluded), or null when there
165
- * is nothing to measure. Reads the engine's bounds — which cover a loaded GLB and every child —
166
- * and falls back to a Mesh node's own CPU geometry on hosts without them.
167
- */
168
- static boundsOf(node: Node): ShapeBounds | null {
169
- const box = _creator.computeBoundingBox?.(node.id)
170
- if (box && (box[3] - box[0] > 1e-6 || box[4] - box[1] > 1e-6 || box[5] - box[2] > 1e-6)) {
171
- return { min: [box[0], box[1], box[2]], max: [box[3], box[4], box[5]] }
172
- }
173
- const v = node.geometry?.vertices
174
- if (!v || v.length < 3) return null
175
- const min: [number, number, number] = [Infinity, Infinity, Infinity]
176
- const max: [number, number, number] = [-Infinity, -Infinity, -Infinity]
177
- for (let i = 0; i + 2 < v.length; i += 3) {
178
- for (let k = 0; k < 3; k++) {
179
- if (v[i + k] < min[k]) min[k] = v[i + k]
180
- if (v[i + k] > max[k]) max[k] = v[i + k]
181
- }
182
- }
183
- return isFinite(min[0]) ? { min, max } : null
184
- }
185
-
186
- /**
187
- * Dimensions + origin that make `kind` hug what `node` renders — world units, so the node's world
188
- * scale is baked in exactly as the Shape fields expect. Null when the node has no geometry (a
189
- * model that hasn't loaded, an empty).
190
- */
191
- static fitTo(node: Node, kind: ShapeKind = "box"): ShapeFit | null {
192
- const b = Shape.boundsOf(node)
193
- return b ? Shape.fitBounds(b, node.worldScale, kind) : null
194
- }
195
-
196
- /**
197
- * The measuring itself, over plain numbers: a node-local AABB + the node's world scale in, shape
198
- * dimensions out. Separate from `fitTo` so the scene-editor harness can measure a node from
199
- * ANOTHER bundle without touching accessors the bundler may have shaken out of it.
200
- *
201
- * A sphere takes the LARGEST half-extent and a cylinder/capsule the larger of X/Z, so the fit
202
- * reads as the object's silhouette rather than as an enclosing ball; adjust from there.
203
- */
204
- static fitBounds(bounds: ShapeBounds, scale: Vec3Like, kind: ShapeKind = "box"): ShapeFit {
205
- const sc = [Math.abs(cx(scale)), Math.abs(cy(scale)), Math.abs(cz(scale))]
206
- const half = [0, 1, 2].map((k) => (bounds.max[k] - bounds.min[k]) * 0.5 * sc[k])
207
- const origin = [0, 1, 2].map((k) => r4((bounds.max[k] + bounds.min[k]) * 0.5 * sc[k])) as [number, number, number]
208
- if (kind === "mesh" || kind === "convex") return { origin: [0, 0, 0] } // the mesh IS the geometry
209
- if (kind === "sphere") return { sphere: dim(Math.max(half[0], half[1], half[2])), origin }
210
- if (kind === "cylinder" || kind === "capsule") {
211
- const radius = dim(Math.max(half[0], half[2]))
212
- const halfHeight = kind === "capsule" ? Math.max(0, r4(half[1] - radius)) : dim(half[1])
213
- return { [kind]: { halfHeight, radius }, origin }
214
- }
215
- return { box: [dim(half[0]), dim(half[1]), dim(half[2])], origin }
216
- }
217
-
218
- // ---- body/shape lifetime --------------------------------------------------------------------
219
-
220
- /** @internal Physics/Trigger/CharacterController/Vehicle take over: drop the pick body and hand back
221
- * the shape id. The node's transform owner goes with the body (the new owner sets its own). */
222
- _claim(): number {
223
- if (this._bodyId) { _creator.physicsRemoveBody(this._bodyId); this._bodyId = 0 }
224
- this.node._xf = 0
225
- this.node._xfKind = 0
226
- return this._shapeId
227
- }
228
- /** @internal Physics/Trigger report the body they created, so Shape owns its lifetime + pickability.
229
- * 0 = they are detaching and no longer own it. */
230
- _ownBody(bodyId: number): void {
231
- this._bodyId = bodyId
232
- this.node._xf = bodyId // route node.position writes to the body (see Node._setOwnedPosition)
233
- this.node._xfKind = bodyId ? 2 : 0
234
- if (!this.raycast && bodyId) _creator.physicsSetPickable(bodyId, false)
235
- }
236
- /** @internal restore a pick-only body when Physics/Trigger detach but the Shape remains. */
237
- _recreatePickBody(): void {
238
- if (!this._bodyId && this._shapeId) this._createPickBody()
239
- }
240
-
241
- private _createPickBody(): void {
242
- this._bodyId = _creator.physicsCreateBody(this.node.id, this._shapeId, 0, 0, false, /*pickOnly*/ true)
243
- // Route position writes here too: the pick body is a static snapshot, so without this a moved node
244
- // would keep taking its clicks at the old place.
245
- this.node._xf = this._bodyId
246
- this.node._xfKind = this._bodyId ? 2 : 0
247
- if (!this.raycast && this._bodyId) _creator.physicsSetPickable(this._bodyId, false)
248
- }
249
-
250
- /** @internal true for an exact triangle mesh or a height field — Physics refuses a dynamic body on it. */
251
- get _isTriangleMesh(): boolean { return this.mesh === true || this.heightfield === true }
252
-
253
- /** @internal the height range a heightfield shape can encode (`Terrain.commit` rebuilds past it). */
254
- _heightfieldRange: [number, number] | null = null
255
-
256
- /** @internal rebuild the geometry in place (a terrain edit that left the encodable range). */
257
- _rebuildShape(): void { this._rebuild() }
258
-
259
- // Rebuild the geometry in place: the body keeps its id, velocity and world transform (Physics
260
- // holds that id, so re-creating the body instead would strand it). A shape a CharacterController
261
- // or a Vehicle claimed is theirs — they were handed the old one and keep it.
262
- private _rebuild(): void {
263
- if (!this._shapeId) return // never attached, or a build without physics
264
- const previous = this._shapeId
265
- const previousSig = this._sig
266
- const id = this._build()
267
- if (!id) return // build failed — keep what works
268
-
269
- // A CharacterController owns its shape and can REFUSE the swap: standing up under a pipe doesn't
270
- // fit. Nothing is half-applied on refusal, so the fix is to stay UNCOMMITTED — leave `_sig` on the
271
- // old geometry, and the next `node.aspect(Shape, …)` with the same dimensions sees a difference
272
- // and tries again. That retry is the headroom test, and it is exact (the real capsule, not a ray).
273
- const charId = this.node._xfKind === 1 ? this.node._xf : 0
274
- if (charId) {
275
- if (_creator.characterSetShape?.(charId, id)) {
276
- this._shapeId = id
277
- this._resizeRefused = false
278
- _creator.physicsDestroyShape(previous)
279
- } else {
280
- _creator.physicsDestroyShape(id) // drop what we just built; the old collider stays live
281
- this._sig = previousSig
282
- this._resizeRefused = !!_creator.characterSetShape
283
- if (!_creator.characterSetShape) {
284
- console.warn(`Shape: this host can't resize a live CharacterController — "${this.node.name}" keeps its current collider`)
285
- }
286
- }
287
- return
288
- }
289
-
290
- this._shapeId = id
291
- if (this._bodyId) {
292
- if (_creator.physicsSetBodyShape) _creator.physicsSetBodyShape(this._bodyId, id, true)
293
- else console.warn("Shape: this host can't re-shape a live body — the new dimensions apply from the next attach")
294
- } else {
295
- console.warn(`Shape: "${this.node.name}"'s geometry is owned by a Vehicle — re-attach it to pick up the new dimensions`)
296
- }
297
- _creator.physicsDestroyShape(previous)
298
- }
299
-
300
- /** @internal True while a requested resize has been refused — the fields say one thing, the engine
301
- * is still using another. Surfaced as `controller.resizing`. */
302
- _resizeRefused = false
303
-
304
- private _build(): number {
305
- this._autoOrigin = null
306
- this._sig = this._signature()
307
- let id = 0
308
- if (this.heightfield) id = this._buildHeightField()
309
- else if (this.mesh) id = this._buildMesh(this.mesh === "convex")
310
- else if (this.sphere !== undefined) id = _creator.physicsBuildSphere(this.sphere)
311
- else if (this.cylinder) id = _creator.physicsBuildCylinder(this.cylinder.halfHeight, this.cylinder.radius)
312
- else if (this.capsule) id = _creator.physicsBuildCapsule(this.capsule.halfHeight, this.capsule.radius)
313
- else if (this.box) id = _creator.physicsBuildBox(cx(this.box), cy(this.box), cz(this.box))
314
- else id = this._buildAutoBox()
315
- if (id) this._applyOrigin(id)
316
- return id
317
- }
318
-
319
- // Push `origin` (or, for an auto box, the mesh centre it measured) onto the built shape. A host
320
- // without the call keeps the old "centred on the node" behaviour; only an EXPLICIT origin warns,
321
- // since the auto centre is a silent improvement nobody asked for.
322
- private _applyOrigin(shapeId: number): void {
323
- const o = this.origin
324
- const x = o ? cx(o) : this._autoOrigin?.[0] ?? 0
325
- const y = o ? cy(o) : this._autoOrigin?.[1] ?? 0
326
- const z = o ? cz(o) : this._autoOrigin?.[2] ?? 0
327
- if (x === 0 && y === 0 && z === 0) return
328
- if (!_creator.physicsSetShapeOrigin) {
329
- if (o) console.warn("Shape { origin } needs a newer host — the collider stays centred on the node")
330
- return
331
- }
332
- _creator.physicsSetShapeOrigin(shapeId, x, y, z)
333
- }
334
-
335
- // What a rebuild has to notice: the kind, its dimensions and the origin (`raycast` is applied to
336
- // the body directly, and the node's own transform is the body's, not the shape's).
337
- private _signature(): string {
338
- const v = (a: Vec3Like | undefined) => (a ? `${cx(a)},${cy(a)},${cz(a)}` : "")
339
- const t = (o: { halfHeight: number, radius: number } | undefined) => (o ? `${o.halfHeight},${o.radius}` : "")
340
- return `${v(this.box)}|${this.sphere ?? ""}|${t(this.cylinder)}|${t(this.capsule)}|${this.mesh ?? ""}|${this.heightfield ? "hf" : ""}|${v(this.origin)}`
341
- }
342
-
343
- // heightfield: the terrain on this node hands its grid over; the engine keeps its own copy and
344
- // pads it to Jolt's block grid. No terrain / no host support = no collider (0), with a warning.
345
- private _buildHeightField(): number {
346
- const t = terrainOf(this.node)
347
- if (!t) { console.warn(`Shape { heightfield }: "${this.node.name}" carries no Terrain — no collider`); return 0 }
348
- if (!_creator.physicsBuildHeightField) { console.warn("Shape { heightfield } needs a newer host — the terrain has no collider"); return 0 }
349
- const s = this.node.worldScale
350
- let lo = Infinity, hi = -Infinity
351
- for (let i = 0; i < t.heights.length; i++) { const h = t.heights[i]; if (h < lo) lo = h; if (h > hi) hi = h }
352
- const range = Math.max(1, hi - lo), margin = Math.max(2, range * 0.25)
353
- this._heightfieldRange = [ lo - margin, hi + margin ] // mirrors creator-gl physicsBuildHeightField's headroom
354
- const id = _creator.physicsBuildHeightField(t.heights, t.sizeX, t.sizeZ, t.cellSize, t.holes, s.x, s.y, s.z)
355
- if (!id) console.warn(`Shape { heightfield }: the engine refused "${this.node.name}"'s grid (all holes?)`)
356
- return id
357
- }
358
-
359
- // mesh: a Mesh node hands its CPU geometry over; anything else is tried as a GLB root (the host
360
- // reads the asset's triangles itself — 0 if the node is neither). Always × world scale: the
361
- // triangles are node-local, so the collider must follow the rendered size.
362
- private _buildMesh(convex: boolean): number {
363
- if (!_creator.physicsBuildMesh) {
364
- console.warn("Shape { mesh } needs a newer host — falling back to a box")
365
- return this._buildAutoBox()
366
- }
367
- const s = this.node.worldScale
368
- const geom = this.node.geometry
369
- let id = 0
370
- if (geom) {
371
- if (geom._meshKind !== 0) throw new Error("Shape { mesh } needs a triangle geometry (not edges/vertices)")
372
- id = _creator.physicsBuildMesh(geom.vertices, Uint32Array.from(geom.indices), convex, s.x, s.y, s.z)
373
- } else {
374
- id = _creator.physicsBuildMeshFromEntity(this.node.id, convex, s.x, s.y, s.z)
375
- }
376
- if (!id) {
377
- console.warn(`Shape { mesh } could not build a ${convex ? "convex hull" : "triangle mesh"} for "${this.node.name}" (no triangles / not a Mesh or Model) — falling back to a box`)
378
- return this._buildAutoBox()
379
- }
380
- return id
381
- }
382
-
383
- // auto: a box from the node's own mesh AABB × world scale (so the physical box matches the
384
- // rendered size), CENTRED on those bounds — geometry modelled off its pivot gets a box that still
385
- // wraps it. A node with no geometry of its own (a Model root, an empty) keeps the 0.5 default;
386
- // `fit()` measures the whole subtree instead.
387
- private _buildAutoBox(): number {
388
- const s = this.node.worldScale
389
- let hx = 0.5, hy = 0.5, hz = 0.5
390
- const geom = this.node.geometry
391
- if (geom) {
392
- const v = geom.vertices
393
- let minX = Infinity, minY = Infinity, minZ = Infinity, maxX = -Infinity, maxY = -Infinity, maxZ = -Infinity
394
- for (let i = 0; i + 2 < v.length; i += 3) {
395
- if (v[i] < minX) minX = v[i]
396
- if (v[i] > maxX) maxX = v[i]
397
- if (v[i + 1] < minY) minY = v[i + 1]
398
- if (v[i + 1] > maxY) maxY = v[i + 1]
399
- if (v[i + 2] < minZ) minZ = v[i + 2]
400
- if (v[i + 2] > maxZ) maxZ = v[i + 2]
401
- }
402
- if (isFinite(minX)) {
403
- hx = (maxX - minX) * 0.5; hy = (maxY - minY) * 0.5; hz = (maxZ - minZ) * 0.5
404
- this._autoOrigin = [(maxX + minX) * 0.5 * s.x, (maxY + minY) * 0.5 * s.y, (maxZ + minZ) * 0.5 * s.z]
405
- }
406
- }
407
- return _creator.physicsBuildBox(hx * s.x, hy * s.y, hz * s.z)
408
- }
409
- }