lecodes-sdk 1.2.0 → 2.0.1

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 (260) hide show
  1. package/README.md +5 -2
  2. package/dist/editor.d.ts +12 -0
  3. package/dist/global.d.ts +4 -8
  4. package/dist/host.d.ts +2 -3
  5. package/dist/types/animate/tween/Animation.d.ts +0 -3
  6. package/dist/types/animate/tween/animateValue.d.ts +4 -2
  7. package/dist/types/animate/tween/easing.d.ts +8 -0
  8. package/dist/types/animate/tween/spec.d.ts +15 -7
  9. package/dist/types/audio/audio.d.ts +2 -1
  10. package/dist/types/canvas/Canvas.d.ts +40 -108
  11. package/dist/types/canvas/gen/cssColor.d.ts +17 -0
  12. package/dist/types/canvas/gen/recorder.d.ts +118 -0
  13. package/dist/types/canvas/gen/spec.d.ts +144 -0
  14. package/dist/types/core/color.d.ts +3 -1
  15. package/dist/types/core/pins.d.ts +18 -0
  16. package/dist/types/g2/Node2D.d.ts +5 -8
  17. package/dist/types/g2/Scene2D.d.ts +6 -2
  18. package/dist/types/gl/Foliage.d.ts +30 -7
  19. package/dist/types/gl/Light.d.ts +8 -0
  20. package/dist/types/gl/Lightmap.d.ts +13 -2
  21. package/dist/types/gl/Material.d.ts +17 -3
  22. package/dist/types/gl/Model.d.ts +6 -2
  23. package/dist/types/gl/Node.d.ts +3 -6
  24. package/dist/types/gl/Scene.d.ts +41 -13
  25. package/dist/types/gl/Texture.d.ts +1 -1
  26. package/dist/types/gl/animation/Locomotion.d.ts +8 -1
  27. package/dist/types/inject.d.ts +11 -11
  28. package/dist/types/inject.editor.d.ts +1 -0
  29. package/dist/types/net/core.d.ts +7 -0
  30. package/dist/types/plugin.d.ts +76 -0
  31. package/dist/types/plugins/gen/camera/sdk/camera.d.ts +24 -0
  32. package/dist/types/plugins/gen/camera/sdk/camera.gen.d.ts +25 -0
  33. package/dist/types/plugins/{geolocation.d.ts → gen/geolocation/sdk/geolocation.d.ts} +2 -20
  34. package/dist/types/plugins/gen/geolocation/sdk/geolocation.gen.d.ts +31 -0
  35. package/dist/types/plugins/{map.d.ts → gen/map/sdk/map.d.ts} +9 -50
  36. package/dist/types/plugins/gen/map/sdk/map.gen.d.ts +53 -0
  37. package/dist/types/plugins/gen/push/sdk/push.d.ts +23 -0
  38. package/dist/types/plugins/gen/push/sdk/push.gen.d.ts +35 -0
  39. package/dist/types/plugins/{qr.d.ts → gen/qr-scanner/sdk/qr-scanner.d.ts} +2 -3
  40. package/dist/types/plugins/gen/qr-scanner/sdk/qr-scanner.gen.d.ts +15 -0
  41. package/dist/types/runtime/app.d.ts +9 -2
  42. package/dist/types/runtime/fetch.d.ts +2 -0
  43. package/dist/types/runtime/input.d.ts +1 -1
  44. package/dist/types/runtime/media.d.ts +6 -10
  45. package/dist/types/runtime/misc.d.ts +4 -1
  46. package/dist/types/runtime/net.d.ts +3 -2
  47. package/dist/types/runtime/touch.d.ts +32 -0
  48. package/dist/types/scene/defineScene.d.ts +43 -2
  49. package/dist/types/scene/editor.d.ts +52 -0
  50. package/dist/types/scene/gizmos.d.ts +7 -4
  51. package/dist/types/ui/NativeView.d.ts +6 -4
  52. package/dist/types/ui/UI.d.ts +1 -1
  53. package/dist/types/ui/UIBottomSheet.d.ts +6 -12
  54. package/dist/types/ui/UIButton.d.ts +12 -14
  55. package/dist/types/ui/UIContainer.d.ts +0 -6
  56. package/dist/types/ui/UIImage.d.ts +1 -4
  57. package/dist/types/ui/UIInput.d.ts +8 -24
  58. package/dist/types/ui/UIModal.d.ts +0 -2
  59. package/dist/types/ui/UINode.d.ts +70 -56
  60. package/dist/types/ui/UIPager.d.ts +28 -27
  61. package/dist/types/ui/UIPopover.d.ts +0 -2
  62. package/dist/types/ui/UIScreen.d.ts +12 -17
  63. package/dist/types/ui/UIScrollable.d.ts +1 -4
  64. package/dist/types/ui/UIText.d.ts +0 -2
  65. package/dist/types/ui/UIVideo.d.ts +3 -5
  66. package/dist/types/ui/UIVirtualizedList.d.ts +14 -16
  67. package/dist/types/ui/UIWidget.d.ts +10 -9
  68. package/dist/types/ui/colorKeys.gen.d.ts +9 -0
  69. package/dist/types/ui/presentable.d.ts +46 -32
  70. package/dist/types/ui/router.d.ts +18 -7
  71. package/dist/types/ui/styleColor.d.ts +1 -0
  72. package/dist/types/ui/transitions.d.ts +18 -0
  73. package/dist/types/ui/tree.d.ts +75 -0
  74. package/dist/types/version.d.ts +10 -0
  75. package/dist/types.json +1 -1
  76. package/package.json +12 -3
  77. package/prompts/2d.md +2 -6
  78. package/prompts/3d.md +1 -5
  79. package/prompts/README.md +1 -1
  80. package/prompts/canvas.md +9 -8
  81. package/prompts/compose.ts +1 -1
  82. package/prompts/core.md +3 -3
  83. package/prompts/design.md +1 -1
  84. package/prompts/dist/2d-game.md +473 -239
  85. package/prompts/dist/3d-app.md +553 -205
  86. package/prompts/dist/ar-app.md +435 -202
  87. package/prompts/dist/design.md +113 -95
  88. package/prompts/dist/ui-app.md +386 -170
  89. package/prompts/ui-design.md +2 -3
  90. package/prompts/ui.md +45 -37
  91. package/src/animate/tween/Animation.ts +34 -150
  92. package/src/animate/tween/Timeline.ts +175 -175
  93. package/src/animate/tween/animateValue.ts +6 -3
  94. package/src/animate/tween/easing.ts +10 -3
  95. package/src/animate/tween/spec.ts +41 -15
  96. package/src/audio/Sound.ts +3 -3
  97. package/src/audio/audio.ts +2 -1
  98. package/src/bridges/2d.d.ts +317 -0
  99. package/src/bridges/app.d.ts +91 -0
  100. package/src/bridges/audio.d.ts +97 -0
  101. package/src/bridges/canvas.d.ts +79 -0
  102. package/src/bridges/device.d.ts +72 -0
  103. package/src/bridges/fetch.d.ts +80 -0
  104. package/src/bridges/files.d.ts +70 -0
  105. package/src/bridges/gl.d.ts +1133 -0
  106. package/src/bridges/input.d.ts +72 -0
  107. package/src/bridges/media.d.ts +55 -0
  108. package/src/bridges/nav.d.ts +71 -0
  109. package/src/bridges/net.d.ts +52 -0
  110. package/src/bridges/service.d.ts +52 -0
  111. package/src/bridges/socket.d.ts +31 -0
  112. package/src/bridges/storage.d.ts +33 -0
  113. package/src/bridges/tree.d.ts +301 -0
  114. package/src/bridges/types.d.ts +49 -0
  115. package/src/canvas/Canvas.ts +114 -159
  116. package/src/canvas/gen/cssColor.ts +224 -0
  117. package/src/canvas/gen/recorder.ts +212 -0
  118. package/src/canvas/gen/spec.ts +201 -0
  119. package/src/chisel.ts +193 -0
  120. package/src/compile/assetMacro.ts +1 -1
  121. package/src/compile/bundler.ts +11 -2
  122. package/src/compile/compileProject.ts +43 -4
  123. package/src/compile/fontMacro.ts +3 -4
  124. package/src/compile/header.ts +26 -5
  125. package/src/compile/index.ts +3 -1
  126. package/src/compile/liteMaterial.ts +1 -1
  127. package/src/compile/sceneEditor.ts +11 -26
  128. package/src/core/color.ts +73 -30
  129. package/src/core/pins.ts +51 -0
  130. package/src/core/signals.ts +8 -1
  131. package/src/g2/CharacterController2D.ts +3 -3
  132. package/src/g2/Node2D.ts +57 -39
  133. package/src/g2/Physics2D.ts +2 -2
  134. package/src/g2/Scene2D.ts +35 -23
  135. package/src/g2/Texture2D.ts +1 -1
  136. package/src/g2/loop.ts +4 -4
  137. package/src/gl/CameraPlace.ts +52 -52
  138. package/src/gl/Foliage.ts +72 -17
  139. package/src/gl/Geometry.ts +1 -2
  140. package/src/gl/Light.ts +10 -0
  141. package/src/gl/Lightmap.ts +45 -29
  142. package/src/gl/Material.ts +95 -51
  143. package/src/gl/Mesh.ts +120 -120
  144. package/src/gl/Model.ts +21 -16
  145. package/src/gl/Node.ts +91 -24
  146. package/src/gl/Particles.ts +1 -1
  147. package/src/gl/Scene.ts +100 -44
  148. package/src/gl/Texture.ts +8 -7
  149. package/src/gl/animation/AnimationClip.ts +1 -1
  150. package/src/gl/animation/DynamicBone.ts +482 -482
  151. package/src/gl/animation/Locomotion.ts +8 -3
  152. package/src/gl/nav/NavMesh.ts +3 -4
  153. package/src/gl/physics/Physics.ts +2 -2
  154. package/src/gl/physics/physicsEvents.ts +3 -3
  155. package/src/gl/scenarios.ts +291 -291
  156. package/src/gl/terrain/Terrain.ts +4 -5
  157. package/src/gl/touch.ts +14 -15
  158. package/src/host.d.ts +2 -3
  159. package/src/inject.editor.ts +7 -0
  160. package/src/inject.ts +13 -16
  161. package/src/net/core.ts +6 -5
  162. package/src/net/index.ts +1 -1
  163. package/src/net/replication.ts +1 -1
  164. package/src/plugin.ts +191 -0
  165. package/src/plugins/gen/camera/contract.d.ts +27 -0
  166. package/src/plugins/gen/camera/sdk/camera.gen.ts +46 -0
  167. package/src/plugins/gen/camera/sdk/camera.ts +57 -0
  168. package/src/plugins/gen/geolocation/contract.d.ts +50 -0
  169. package/src/plugins/gen/geolocation/sdk/geolocation.gen.ts +54 -0
  170. package/src/plugins/{geolocation.ts → gen/geolocation/sdk/geolocation.ts} +22 -43
  171. package/src/plugins/gen/map/contract.d.ts +144 -0
  172. package/src/plugins/gen/map/sdk/map.gen.ts +88 -0
  173. package/src/plugins/{map.ts → gen/map/sdk/map.ts} +68 -102
  174. package/src/plugins/gen/push/contract.d.ts +61 -0
  175. package/src/plugins/gen/push/sdk/push.gen.ts +60 -0
  176. package/src/plugins/gen/push/sdk/push.ts +105 -0
  177. package/src/plugins/gen/qr-scanner/contract.d.ts +16 -0
  178. package/src/plugins/gen/qr-scanner/sdk/qr-scanner.gen.ts +29 -0
  179. package/src/plugins/gen/qr-scanner/sdk/qr-scanner.ts +52 -0
  180. package/src/plugins/permission.ts +5 -4
  181. package/src/runtime/app.ts +20 -9
  182. package/src/runtime/appEvents.ts +5 -4
  183. package/src/runtime/channel.ts +18 -15
  184. package/src/runtime/clipboard.ts +4 -3
  185. package/src/runtime/datetime.ts +2 -1
  186. package/src/runtime/device.ts +17 -15
  187. package/src/runtime/fetch.ts +30 -20
  188. package/src/runtime/files.ts +16 -15
  189. package/src/runtime/input.ts +12 -10
  190. package/src/runtime/media.ts +50 -46
  191. package/src/runtime/misc.ts +7 -3
  192. package/src/runtime/net.ts +8 -7
  193. package/src/runtime/rpc.ts +1 -3
  194. package/src/runtime/service.ts +19 -14
  195. package/src/runtime/share.ts +4 -3
  196. package/src/runtime/storage.ts +6 -4
  197. package/src/runtime/touch.ts +32 -0
  198. package/src/scene/defineScene.ts +61 -365
  199. package/src/scene/editor.ts +408 -0
  200. package/src/scene/editorPlugins.ts +3 -3
  201. package/src/scene/gizmos.ts +15 -9
  202. package/src/server/db/marci/query.ts +1 -1
  203. package/src/server/host.ts +1 -1
  204. package/src/server/runtime.ts +1 -1
  205. package/src/ui/NativeView.ts +72 -25
  206. package/src/ui/UI.ts +3 -3
  207. package/src/ui/UIBottomSheet.ts +16 -17
  208. package/src/ui/UIButton.ts +54 -16
  209. package/src/ui/UIContainer.ts +0 -6
  210. package/src/ui/UIImage.ts +34 -30
  211. package/src/ui/UIInput.ts +29 -37
  212. package/src/ui/UIModal.ts +1 -3
  213. package/src/ui/UINode.ts +347 -297
  214. package/src/ui/UIPager.ts +93 -78
  215. package/src/ui/UIPopover.ts +0 -2
  216. package/src/ui/UIScreen.ts +41 -36
  217. package/src/ui/UIScrollable.ts +19 -13
  218. package/src/ui/UISpacer.ts +1 -1
  219. package/src/ui/UITabs.ts +8 -6
  220. package/src/ui/UIText.ts +7 -17
  221. package/src/ui/UIVideo.ts +30 -27
  222. package/src/ui/UIVirtualizedList.ts +58 -59
  223. package/src/ui/UIWidget.ts +38 -18
  224. package/src/ui/colorKeys.gen.ts +37 -0
  225. package/src/ui/fonts.ts +2 -2
  226. package/src/ui/presentable.ts +59 -42
  227. package/src/ui/router.ts +52 -35
  228. package/src/ui/styleColor.ts +56 -0
  229. package/src/ui/theme.ts +7 -6
  230. package/src/ui/transitions.ts +249 -0
  231. package/src/ui/tree.ts +346 -0
  232. package/src/version.ts +24 -0
  233. package/tests/helpers/engineWorld.ts +11 -0
  234. package/tests/helpers/fakeTree.ts +353 -0
  235. package/tests/helpers/hostStubs.ts +31 -0
  236. package/tests/helpers/index.ts +14 -0
  237. package/tests/helpers/memoryMarci.ts +124 -0
  238. package/tests/helpers/phases.ts +23 -0
  239. package/tests/helpers/preload.ts +18 -0
  240. package/tests/helpers/stubApp.ts +2 -0
  241. package/tests/helpers/stubDevice.ts +2 -0
  242. package/tests/helpers/stubFetch.ts +2 -0
  243. package/tests/helpers/stubInput.ts +2 -0
  244. package/dist/inject.js +0 -4629
  245. package/dist/types/core/registry.d.ts +0 -7
  246. package/dist/types/plugins/camera.d.ts +0 -25
  247. package/dist/types/plugins/push.d.ts +0 -46
  248. package/src/bridges.d.ts +0 -1769
  249. package/src/compile/__tests__/assetIconMacro.test.ts +0 -219
  250. package/src/compile/__tests__/assetMacro.test.ts +0 -100
  251. package/src/compile/__tests__/assetName.test.ts +0 -55
  252. package/src/compile/__tests__/compile.test.ts +0 -310
  253. package/src/compile/__tests__/detectEntry.test.ts +0 -151
  254. package/src/compile/__tests__/fontMacro.test.ts +0 -199
  255. package/src/compile/__tests__/serverSplit.test.ts +0 -27
  256. package/src/core/__tests__/stateMachine.test.ts +0 -132
  257. package/src/core/registry.ts +0 -23
  258. package/src/plugins/camera.ts +0 -81
  259. package/src/plugins/push.ts +0 -132
  260. package/src/plugins/qr.ts +0 -73
package/src/gl/Mesh.ts CHANGED
@@ -1,120 +1,120 @@
1
- // A renderable 3D node from raw geometry — the primitive shapes (box/sphere/cylinder/capsule/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 CapsuleOptions,
8
- type CylinderOptions,
9
- type PlaneOptions,
10
- type SphereOptions,
11
- } from "./Geometry"
12
- import { Material } from "./Material"
13
- import { Node } from "./Node"
14
-
15
- /** Common transform/render options every primitive factory accepts. */
16
- export type MeshOptions = {
17
- material?: Material
18
- position?: Vec3Like
19
- eulerAngles?: Vec3Like
20
- scale?: Vec3Like | number
21
- name?: string
22
- castShadows?: boolean
23
- receiveShadows?: boolean
24
- /** Coarse draw order, 0 (first) … 7 (last); default 4. See `Mesh.renderPriority`. */
25
- renderPriority?: number
26
- }
27
-
28
- export class Mesh extends Node {
29
- private _geometry?: Geometry
30
-
31
- constructor(geometry?: Geometry, material?: Material) {
32
- super()
33
- if (geometry) {
34
- const mat = material ?? Material.unlit()
35
- this._geometry = geometry
36
- this._materials = [ mat ]
37
- _creator.setMesh(this.id, Material.idOf(mat), geometry.vertices, geometry.normals, geometry.indices, geometry.uv, geometry._meshKind, geometry.uv1, geometry.colors)
38
- }
39
- }
40
-
41
- override get geometry(): Geometry | undefined { return this._geometry }
42
- /** Replace the geometry in place — the node, its transform and its material stay, the vertex and
43
- * index buffers are rebuilt. What an editor overlay or a debug drawer redraws with. */
44
- setGeometry(geometry: Geometry): this {
45
- this._geometry = geometry
46
- _creator.setMesh(this.id, Material.idOf(this._materials![0]!), geometry.vertices, geometry.normals, geometry.indices, geometry.uv, geometry._meshKind, geometry.uv1, geometry.colors)
47
- // the host rebuilds the renderable for a new geometry (creator-gl createMesh destroys + builds), which
48
- // resets its priority / culling / shadow flags to the defaults — an overlay redrawn every frame lost its
49
- // renderPriority 7 after one frame and hid inside the model. What was set on this mesh is set again.
50
- if (this._renderPriority !== undefined) this.renderPriority = this._renderPriority
51
- if (this._culling !== undefined) this.culling = this._culling
52
- if (this._castShadows !== undefined) this.castShadows = this._castShadows
53
- if (this._receiveShadows !== undefined) this.receiveShadows = this._receiveShadows
54
- return this
55
- }
56
- private _renderPriority?: number
57
- private _culling?: boolean
58
- private _castShadows?: boolean
59
- private _receiveShadows?: boolean
60
-
61
- /** A Mesh always carries a material (slot 0) — see Node.setMaterial for the slot API. */
62
- override get material(): Material { return this._materials![0]! }
63
- override set material(m: Material) { this.setMaterial(m, 0) }
64
-
65
- set culling(v: boolean) { this._culling = v; _creator.setCulling(this.id, v) }
66
- /** Coarse draw order within the frame: 0 draws first, 7 last, 4 is the default (Filament's
67
- * renderable priority; within one priority opaque draws sort front-to-back, blended back-to-front).
68
- * Something drawn over the scene with `Material.depthTest = false` goes in 7, so nothing drawn
69
- * after it can cover it. Write-only; a host that predates the call ignores it. */
70
- set renderPriority(v: number) {
71
- this._renderPriority = v
72
- if (_creator.setRenderPriority) _creator.setRenderPriority(this.id, Math.max(0, Math.min(7, Math.round(v))))
73
- else if (!Mesh._warnedPriority) { Mesh._warnedPriority = true; console.warn("[mesh] this host has no setRenderPriority — renderPriority ignored") }
74
- }
75
- private static _warnedPriority = false
76
- set castShadows(v: boolean) { this._castShadows = v; _creator.setCastShadows(this.id, v) }
77
- set receiveShadows(v: boolean) { this._receiveShadows = v; _creator.setReceiveShadows(this.id, v) }
78
-
79
- // --- factories ---
80
-
81
- static box(options: MeshOptions & { size?: Vec3Like | number } = {}): Mesh {
82
- // the size goes into the geometry (not a scale after): the lightmap chart is laid out per face area
83
- return apply(new Mesh(Geometry.box(options.size ?? 1), options.material), options)
84
- }
85
-
86
- static sphere(options: MeshOptions & SphereOptions = {}): Mesh {
87
- return apply(new Mesh(Geometry.sphere(options), options.material), options)
88
- }
89
-
90
- static cylinder(options: MeshOptions & CylinderOptions = {}): Mesh {
91
- return apply(new Mesh(Geometry.cylinder(options), options.material), options)
92
- }
93
-
94
- /** A capsule along Y (`radius`, `length` between the cap centres) — a character's or a collider's shape in one mesh. */
95
- static capsule(options: MeshOptions & CapsuleOptions = {}): Mesh {
96
- return apply(new Mesh(Geometry.capsule(options), options.material), options)
97
- }
98
-
99
- static plane(options: MeshOptions & PlaneOptions = {}): Mesh {
100
- return apply(new Mesh(Geometry.plane(options), options.material), options)
101
- }
102
-
103
- /** Wrap a custom Geometry. */
104
- static from(geometry: Geometry, options: MeshOptions = {}): Mesh {
105
- return apply(new Mesh(geometry, options.material), options)
106
- }
107
- }
108
-
109
- const sizeToVec = (s: Vec3Like | number): [number, number, number] => (typeof s === "number" ? [ s, s, s ] : [ cx(s), cy(s), cz(s) ])
110
-
111
- const apply = (mesh: Mesh, o: MeshOptions): Mesh => {
112
- if (o.position) mesh.position = o.position
113
- if (o.eulerAngles) mesh.eulerAngles = o.eulerAngles
114
- if (o.scale !== undefined) mesh.scale = typeof o.scale === "number" ? [ o.scale, o.scale, o.scale ] : o.scale
115
- if (o.name !== undefined) mesh.name = o.name
116
- if (o.castShadows !== undefined) mesh.castShadows = o.castShadows
117
- if (o.receiveShadows !== undefined) mesh.receiveShadows = o.receiveShadows
118
- if (o.renderPriority !== undefined) mesh.renderPriority = o.renderPriority
119
- return mesh
120
- }
1
+ // A renderable 3D node from raw geometry — the primitive shapes (box/sphere/cylinder/capsule/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 CapsuleOptions,
8
+ type CylinderOptions,
9
+ type PlaneOptions,
10
+ type SphereOptions,
11
+ } from "./Geometry"
12
+ import { Material } from "./Material"
13
+ import { Node } from "./Node"
14
+
15
+ /** Common transform/render options every primitive factory accepts. */
16
+ export type MeshOptions = {
17
+ material?: Material
18
+ position?: Vec3Like
19
+ eulerAngles?: Vec3Like
20
+ scale?: Vec3Like | number
21
+ name?: string
22
+ castShadows?: boolean
23
+ receiveShadows?: boolean
24
+ /** Coarse draw order, 0 (first) … 7 (last); default 4. See `Mesh.renderPriority`. */
25
+ renderPriority?: number
26
+ }
27
+
28
+ export class Mesh extends Node {
29
+ private _geometry?: Geometry
30
+
31
+ constructor(geometry?: Geometry, material?: Material) {
32
+ super()
33
+ if (geometry) {
34
+ const mat = material ?? Material.unlit()
35
+ this._geometry = geometry
36
+ this._materials = [ mat ]
37
+ _creator.setMesh(this.id, Material.idOf(mat), geometry.vertices, geometry.normals, geometry.indices, geometry.uv, geometry._meshKind, geometry.uv1, geometry.colors)
38
+ }
39
+ }
40
+
41
+ override get geometry(): Geometry | undefined { return this._geometry }
42
+ /** Replace the geometry in place — the node, its transform and its material stay, the vertex and
43
+ * index buffers are rebuilt. What an editor overlay or a debug drawer redraws with. */
44
+ setGeometry(geometry: Geometry): this {
45
+ this._geometry = geometry
46
+ _creator.setMesh(this.id, Material.idOf(this._materials![0]!), geometry.vertices, geometry.normals, geometry.indices, geometry.uv, geometry._meshKind, geometry.uv1, geometry.colors)
47
+ // the host rebuilds the renderable for a new geometry (creator-gl createMesh destroys + builds), which
48
+ // resets its priority / culling / shadow flags to the defaults — an overlay redrawn every frame lost its
49
+ // renderPriority 7 after one frame and hid inside the model. What was set on this mesh is set again.
50
+ if (this._renderPriority !== undefined) this.renderPriority = this._renderPriority
51
+ if (this._culling !== undefined) this.culling = this._culling
52
+ if (this._castShadows !== undefined) this.castShadows = this._castShadows
53
+ if (this._receiveShadows !== undefined) this.receiveShadows = this._receiveShadows
54
+ return this
55
+ }
56
+ private _renderPriority?: number
57
+ private _culling?: boolean
58
+ private _castShadows?: boolean
59
+ private _receiveShadows?: boolean
60
+
61
+ /** A Mesh always carries a material (slot 0) — see Node.setMaterial for the slot API. */
62
+ override get material(): Material { return this._materials![0]! }
63
+ override set material(m: Material) { this.setMaterial(m, 0) }
64
+
65
+ set culling(v: boolean) { this._culling = v; _creator.setCulling(this.id, v) }
66
+ /** Coarse draw order within the frame: 0 draws first, 7 last, 4 is the default (Filament's
67
+ * renderable priority; within one priority opaque draws sort front-to-back, blended back-to-front).
68
+ * Something drawn over the scene with `Material.depthTest = false` goes in 7, so nothing drawn
69
+ * after it can cover it. Write-only; a host that predates the call ignores it. */
70
+ set renderPriority(v: number) {
71
+ this._renderPriority = v
72
+ if (_creator.setRenderPriority) _creator.setRenderPriority(this.id, Math.max(0, Math.min(7, Math.round(v))))
73
+ else if (!Mesh._warnedPriority) { Mesh._warnedPriority = true; console.warn("[mesh] this host has no setRenderPriority — renderPriority ignored") }
74
+ }
75
+ private static _warnedPriority = false
76
+ set castShadows(v: boolean) { this._castShadows = v; _creator.setCastShadows(this.id, v) }
77
+ set receiveShadows(v: boolean) { this._receiveShadows = v; _creator.setReceiveShadows(this.id, v) }
78
+
79
+ // --- factories ---
80
+
81
+ static box(options: MeshOptions & { size?: Vec3Like | number } = {}): Mesh {
82
+ // the size goes into the geometry (not a scale after): the lightmap chart is laid out per face area
83
+ return apply(new Mesh(Geometry.box(options.size ?? 1), options.material), options)
84
+ }
85
+
86
+ static sphere(options: MeshOptions & SphereOptions = {}): Mesh {
87
+ return apply(new Mesh(Geometry.sphere(options), options.material), options)
88
+ }
89
+
90
+ static cylinder(options: MeshOptions & CylinderOptions = {}): Mesh {
91
+ return apply(new Mesh(Geometry.cylinder(options), options.material), options)
92
+ }
93
+
94
+ /** A capsule along Y (`radius`, `length` between the cap centres) — a character's or a collider's shape in one mesh. */
95
+ static capsule(options: MeshOptions & CapsuleOptions = {}): Mesh {
96
+ return apply(new Mesh(Geometry.capsule(options), options.material), options)
97
+ }
98
+
99
+ static plane(options: MeshOptions & PlaneOptions = {}): Mesh {
100
+ return apply(new Mesh(Geometry.plane(options), options.material), options)
101
+ }
102
+
103
+ /** Wrap a custom Geometry. */
104
+ static from(geometry: Geometry, options: MeshOptions = {}): Mesh {
105
+ return apply(new Mesh(geometry, options.material), options)
106
+ }
107
+ }
108
+
109
+ const sizeToVec = (s: Vec3Like | number): [number, number, number] => (typeof s === "number" ? [ s, s, s ] : [ cx(s), cy(s), cz(s) ])
110
+
111
+ const apply = (mesh: Mesh, o: MeshOptions): Mesh => {
112
+ if (o.position) mesh.position = o.position
113
+ if (o.eulerAngles) mesh.eulerAngles = o.eulerAngles
114
+ if (o.scale !== undefined) mesh.scale = typeof o.scale === "number" ? [ o.scale, o.scale, o.scale ] : o.scale
115
+ if (o.name !== undefined) mesh.name = o.name
116
+ if (o.castShadows !== undefined) mesh.castShadows = o.castShadows
117
+ if (o.receiveShadows !== undefined) mesh.receiveShadows = o.receiveShadows
118
+ if (o.renderPriority !== undefined) mesh.renderPriority = o.renderPriority
119
+ return mesh
120
+ }
package/src/gl/Model.ts CHANGED
@@ -5,7 +5,7 @@
5
5
  // hero.anim.play('Run', { loop: true })
6
6
 
7
7
  import { fetch, type FetchResponse } from "../runtime/fetch"
8
- import { Node } from "./Node"
8
+ import { Node, nodes } from "./Node"
9
9
  import { Animator, _pushLod, type LodMode } from "./animation/Animator"
10
10
  import { Material } from "./Material"
11
11
 
@@ -20,12 +20,16 @@ export interface TrianglePick {
20
20
  mesh: string
21
21
  primitive: number
22
22
  material: string
23
+ /** the node's lightmap group (scene-file extras), when it has one */
24
+ lightmapGroup?: number
23
25
  /** triangle index within the primitive (index-buffer order), and whether the ray came from behind */
24
26
  triangle: number
25
27
  backface: boolean
26
28
  distance: number
27
29
  point: [number, number, number]
28
30
  bary: [number, number, number]
31
+ /** the lightmap uv (TEXCOORD_1) at the hit, when the primitive carries one */
32
+ uv1?: [number, number]
29
33
  skin: string
30
34
  vertices: { index: number, bind: [number, number, number], world: [number, number, number], bones: { name: string, weight: number }[] }[]
31
35
  }
@@ -47,7 +51,7 @@ export class Model extends Node {
47
51
  /** @internal -1 = auto (see `lod`) */
48
52
  _lodMesh = -1
49
53
 
50
- constructor(internalId: number) {
54
+ constructor(internalId: number | Handle) {
51
55
  super(internalId)
52
56
  this.aspect(Animator)
53
57
  }
@@ -95,16 +99,16 @@ export class Model extends Node {
95
99
  * or misbound polygon can be traced to its binding. One full CPU skin of the model per call: click-rate
96
100
  * only. null = miss, or a host without the pick (desktop today). */
97
101
  pickTriangle(screenX: number, screenY: number): TrianglePick | null {
98
- const json = _creator.pickTriangle?.(this.id, screenX, screenY)
99
- return json ? JSON.parse(json) as TrianglePick : null
102
+ return _creator.pickTriangle?.(this.id, screenX, screenY) ?? null
100
103
  }
101
104
 
102
105
  /** Duplicate this model — a deep copy of the GLB (meshes, skeleton, animation clips), attached to
103
106
  * the same parent and scene and sharing this model's current transform. The clone has its own
104
107
  * independent animation state (reach it via clone.anim). Mirrors this model's culling flag. */
105
108
  clone(): Model {
106
- const id = _creator.cloneEntity(this.id)
107
- const m = new Model(id)
109
+ const m = new Model(_creator.cloneEntity(this.id))
110
+ const id = m.id
111
+ if (_creator.entityIsOwned(id)) nodes.pin(m) // cloned into this model's parent / scene: native owns it
108
112
  m._lightmapped = this._lightmapped
109
113
  m._foliage = this._foliage
110
114
  if (!this._culling) { m._culling = false; _creator.setGlbCulling(id, false) } // the engine default is on
@@ -124,7 +128,7 @@ export class Model extends Node {
124
128
  /** Frustum culling — see `culling` (default: on where the host refits skinned bounds, else off). */
125
129
  culling?: boolean
126
130
  onProgress?: (p: { loaded: number, total?: number }) => void
127
- /** Baked lighting (packages/creator-bake). `true` = a STATIC: loads through the lightmap material so
131
+ /** Baked lighting (engines/bake). `true` = a STATIC: loads through the lightmap material so
128
132
  * `Lightmap.load` can bind its atlas rect (the GLB needs TEXCOORD_1 — `lecodes assets doctor
129
133
  * --lightmap-uv`) and takes nothing from the real-time lights once the bake applies. Omitted / `false` =
130
134
  * the standard shader, lit real-time (movers). Hosts without lightmap support ignore it. */
@@ -141,21 +145,22 @@ export class Model extends Node {
141
145
  })
142
146
  }
143
147
  return new Promise<Model>((resolve, reject) => {
144
- // the provider path: a foliage model takes the vegetation tier's pair when the host ships it (fetchLocal
145
- // id 0 = it does not), a static the lightmap material + its masked twin; anything else the ubershader
148
+ // the provider path: a foliage model takes the vegetation tier's pair when the host carries it, a
149
+ // static the lightmap material + its masked twin; anything else the ubershader
146
150
  const foliageWanted = !!options.foliage && !!_creator.setNextGlbLightmapped
147
151
  let foliage = false
148
152
  if (foliageWanted) {
149
- const fol = Material.idOf(Material._foliageTemplate())
150
- const folMasked = fol ? Material.idOf(Material._foliageMaskedTemplate()) : 0
151
- foliage = fol > 0 && folMasked > 0
152
- if (foliage) _creator.setNextGlbLightmapped!(fol, folMasked)
153
+ const fol = Material._foliageTemplate()
154
+ const folMasked = fol && Material._foliageMaskedTemplate()
155
+ foliage = !!fol && !!folMasked
156
+ if (fol && folMasked) _creator.setNextGlbLightmapped!(Material.idOf(fol), Material.idOf(folMasked))
153
157
  }
154
158
  const lightmapped = !foliage && !!options.lightmap && !!_creator.setNextGlbLightmapped
155
159
  if (lightmapped) _creator.setNextGlbLightmapped!(Material.idOf(Material._lightmapTemplate()), Material.idOf(Material._lightmapMaskedTemplate()))
156
- _creator.createGlb((source as unknown as { _id: number })._id, (entityId: number) => {
157
- if (entityId === 0) { reject(new Error("Failed to load GLB")); return }
158
- const model = new Model(entityId)
160
+ _creator.createGlb((source as unknown as { _h: Handle })._h.id, (root) => {
161
+ if (!root) { reject(new Error("Failed to load GLB")); return }
162
+ const model = new Model(root)
163
+ const entityId = model.id
159
164
  model._lightmapped = lightmapped
160
165
  model._foliage = foliage
161
166
  const culling = options.culling ?? Model._cullingDefault()
package/src/gl/Node.ts CHANGED
@@ -6,7 +6,8 @@
6
6
  // A GLB import is the Model node kind (with the Animator aspect at model.anim). Hierarchy lives here.
7
7
 
8
8
  import { AspectHost } from "../core/Aspect"
9
- import { Registry } from "../core/registry"
9
+ import { DEAD_HANDLE, Pins, registerFreedKind } from "../core/pins"
10
+ import { OWNED_KIND_GL_ENTITY } from "../ui/tree"
10
11
  import { Vec3, cx, cy, cz, type Vec3Like } from "../math/vec"
11
12
  import { Quat, cw, type QuatLike } from "../math/quat"
12
13
  import { Mat4, type Mat4Like } from "../math/mat4"
@@ -31,7 +32,38 @@ export type NodeTweenProps = {
31
32
  }
32
33
 
33
34
  /** id → Node, so host callbacks (touch hits, animation events) route back to the owning object. */
34
- export const nodeRegistry = new Registry<Node>()
35
+ /** @internal The nodes native owns right now (in a scene / under a parent, or engine-created), by
36
+ * entity id — what a pick, a contact or an animation event resolves to. See core/pins.ts. */
37
+ export const nodes = new Pins<Node>()
38
+
39
+ // The loose wrappers, WEAKLY: a read-back (`parent` / `children` / `traverse` / `find`) must hand
40
+ // back the app's OWN handle while it exists. A loose root has no native owner, so no pin — the
41
+ // only other way to reach it from below was a throwaway wrapper, and anything put on that wrapper
42
+ // (IK's rig Animator on a bare tree's top) was lost to the app's handle. WeakRef keeps the owned
43
+ // model intact: the registry never holds a handle alive, a dropped root still finalizes, and a
44
+ // dead entry is dropped when met (or in the sweep every SWEEP registrations).
45
+ const loose = new Map<number, WeakRef<Node>>()
46
+ const SWEEP = 256
47
+ let sinceSweep = 0
48
+ const registerLoose = (n: Node): void => {
49
+ loose.set(n._h.id, new WeakRef(n))
50
+ if (++sinceSweep < SWEEP) return
51
+ sinceSweep = 0
52
+ for (const [id, ref] of loose) { const w = ref.deref(); if (!w || w._h.id !== id) loose.delete(id) }
53
+ }
54
+ /** @internal The wrapper for an entity id the runtime handed back: the pinned one, else the
55
+ * app's live loose handle, else a throwaway over the engine-owned id. */
56
+ export const nodeOf = (id: number): Node => {
57
+ const pinned = nodes.get(id)
58
+ if (pinned) return pinned
59
+ const ref = loose.get(id)
60
+ if (ref) {
61
+ const w = ref.deref()
62
+ if (w && w._h.id === id) return w
63
+ loose.delete(id)
64
+ }
65
+ return new Node(id)
66
+ }
35
67
 
36
68
  export type NodeEvents = {
37
69
  click: (ev: ClickEvent<Node | null>) => void
@@ -67,8 +99,12 @@ const findNodeByWalk = (rootId: number, name: string): number => {
67
99
  const worldScratch = new Float32Array(3)
68
100
 
69
101
  export class Node extends AspectHost<NodeEvents> implements CompWriter {
70
- /** Native entity handle. */
71
- readonly id: number
102
+ /** @internal The entity's owned handle (`_h.id` = the entity id; DEAD_HANDLE once destroyed). An
103
+ * engine-created entity (a glTF part, an anchor) is wrapped from its number and pinned as the
104
+ * engine's; an entity WE created is loose until it gets a parent or a scene. */
105
+ _h: Handle
106
+ /** Native entity id. */
107
+ get id(): number { return this._h.id }
72
108
 
73
109
  /** AR anchors only — whether the anchor is currently being tracked. Kept in step with the
74
110
  * 'track' / 'untrack' events; `false` for any non-anchor node. */
@@ -80,10 +116,20 @@ export class Node extends AspectHost<NodeEvents> implements CompWriter {
80
116
  /** Materials assigned through setMaterial, by primitive slot (a Mesh fills slot 0 itself). */
81
117
  protected _materials?: Material[]
82
118
 
83
- constructor(internalId?: number) {
119
+ constructor(internalId?: number | Handle) {
84
120
  super()
85
- this.id = internalId ?? _creator.createEntity()
86
- nodeRegistry.set(this.id, this)
121
+ registerFreedKind(OWNED_KIND_GL_ENTITY, nodes)
122
+ if (internalId === undefined) {
123
+ const h = _creator.createEntity()
124
+ this._h = typeof h === "number" ? { id: h } : h // a number only from a test stub
125
+ registerLoose(this)
126
+ } else if (typeof internalId === "number") {
127
+ this._h = { id: internalId } // engine-owned: findable while the engine keeps it
128
+ nodes.pin(this)
129
+ } else {
130
+ this._h = internalId // an owned root the runtime handed us (createGlb, cloneEntity)
131
+ registerLoose(this)
132
+ }
87
133
  }
88
134
 
89
135
  /** Geometry, if this node is a Mesh (used by physics auto-shape). Overridden by Mesh. */
@@ -263,29 +309,37 @@ export class Node extends AspectHost<NodeEvents> implements CompWriter {
263
309
  // --- hierarchy ---
264
310
  get parent(): Node | null {
265
311
  const id = _creator.getParent(this.id)
266
- return id === 0 ? null : (nodeRegistry.get(id) ?? new Node(id))
312
+ return id === 0 ? null : nodeOf(id)
267
313
  }
268
314
  get children(): Node[] {
269
- return _creator.getChildren(this.id).map((id) => nodeRegistry.get(id) ?? new Node(id))
315
+ const n = _creator.getChildCount(this.id)
316
+ const out: Node[] = []
317
+ for (let i = 0; i < n; i++) { const id = _creator.getChild(this.id, i); out.push(nodeOf(id)) }
318
+ return out
270
319
  }
271
320
  get childCount(): number { return _creator.getChildCount(this.id) }
272
321
  getChild(index: number): Node | null {
273
322
  const id = _creator.getChild(this.id, index)
274
- return id === 0 ? null : (nodeRegistry.get(id) ?? new Node(id))
323
+ return id === 0 ? null : nodeOf(id)
275
324
  }
276
325
  /** Parent the given nodes under this one. */
277
326
  add(...children: Node[]): this {
278
- for (const c of children) _creator.setParent(c.id, this.id, false)
327
+ for (const c of children) { _creator.setParent(c.id, this.id, false); nodes.pin(c) }
279
328
  return this
280
329
  }
281
330
  setParent(parent: Node | null, worldPositionStays = false): this {
282
- if (parent === null) _creator.setParentNull(this.id, worldPositionStays)
283
- else _creator.setParent(this.id, parent.id, worldPositionStays)
331
+ if (parent === null) {
332
+ _creator.setParentNull(this.id, worldPositionStays)
333
+ if (!_creator.entityIsOwned(this.id)) nodes.unpin(this.id) // loose again: the handle owns it
334
+ } else {
335
+ _creator.setParent(this.id, parent.id, worldPositionStays)
336
+ nodes.pin(this)
337
+ }
284
338
  return this
285
339
  }
286
340
  traverse(callback: (node: Node) => void): void {
287
341
  callback(this)
288
- _creator.traverse(this.id, (id) => callback(nodeRegistry.get(id) ?? new Node(id)))
342
+ _creator.traverse(this.id, (id) => callback(nodeOf(id)))
289
343
  }
290
344
  // --- materials (any renderable: a Mesh, or an internal node of a loaded Model) ---
291
345
  /** The material assigned to slot 0 through this API (null before one is set — a GLB part's own
@@ -313,7 +367,7 @@ export class Node extends AspectHost<NodeEvents> implements CompWriter {
313
367
  const hit = cache.get(name)
314
368
  if (hit !== undefined) return hit
315
369
  const id = _creator.findNode ? _creator.findNode(this.id, name) : findNodeByWalk(this.id, name)
316
- const found = id ? nodeRegistry.get(id) ?? new Node(id) : null
370
+ const found = id ? nodeOf(id) : null
317
371
  cache.set(name, found)
318
372
  return found
319
373
  }
@@ -372,16 +426,29 @@ export class Node extends AspectHost<NodeEvents> implements CompWriter {
372
426
  * `destroyEntity` frees the entity tree but knows nothing about JS-side aspects, and a destroyed
373
427
  * node's `update()` must not keep ticking. Deepest nodes go first, then this one. */
374
428
  destroy(): void {
429
+ if (this._h.id === 0) return
375
430
  const subtree: Node[] = [this]
376
- _creator.traverse(this.id, (id) => { const n = nodeRegistry.get(id); if (n) subtree.push(n) })
377
- for (let i = subtree.length - 1; i >= 0; i--) {
378
- const n = subtree[i]
379
- n._detachAll()
380
- n._xf = 0
381
- n._xfKind = 0
382
- nodeRegistry.delete(n.id)
383
- }
384
- _creator.destroyEntity(this.id)
431
+ _creator.traverse(this.id, (id) => { const n = nodes.get(id); if (n) subtree.push(n) })
432
+ for (let i = subtree.length - 1; i >= 0; i--) subtree[i]._detachAll()
433
+ const id = this.id
434
+ // Dead NOW — entity ids are recycled, and the runtime's freed report lands a drain later.
435
+ for (const n of subtree) { nodes.unpin(n.id); n._dead() }
436
+ _creator.destroyEntity(id)
437
+ }
438
+
439
+ /** @internal The wrapper's resource is gone: no more native calls from here. */
440
+ _dead(): void {
441
+ this._xf = 0
442
+ this._xfKind = 0
443
+ loose.delete(this._h.id)
444
+ this._h = DEAD_HANDLE
445
+ }
446
+ /** @internal The runtime freed the entity underneath (an ancestor destroyed or dropped): the
447
+ * aspects detach (their onDetach runs after the entity is gone) and the wrapper is dead. */
448
+ _freed(): void {
449
+ if (this._h.id === 0) return
450
+ this._detachAll()
451
+ this._dead()
385
452
  }
386
453
  }
387
454
 
@@ -4,7 +4,7 @@
4
4
  // All emitter/curve config crosses the bridge as ONE Float32Array of [tag, payloadLen, ...payload]
5
5
  // records (_creator.setParticleSystemConfig) — the constructor batches every option into a single
6
6
  // call, live setters send a one-record buffer. The tag values mirror CPART_TAG_* in
7
- // creator-particles/include/creator-particles/creator-particles.h (guarded by tests/particles-tags.test.ts).
7
+ // creator-particles/include/creator-particles/creator-particles.h (guarded by tests/gl/particles-tags.test.ts).
8
8
  //
9
9
  // Curves over a particle's lifetime are built with curve()/colorCurve() chains — see the builders
10
10
  // below. A builder's whole state is the flat `_data` array (the exact record payload), so the