lecodes-cli 0.19.0 → 0.19.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (254) hide show
  1. package/dist/index.js +730 -235
  2. package/package.json +14 -13
  3. package/runtime/scene-harness.json +1 -1
  4. package/runtime/sdk-types.json +1 -1
  5. package/src/api.ts +302 -0
  6. package/src/browserAuth.ts +87 -0
  7. package/src/cmgenTool.ts +104 -0
  8. package/src/commands/app.ts +891 -0
  9. package/src/commands/appAndroid.ts +603 -0
  10. package/src/commands/appDesktop.ts +326 -0
  11. package/src/commands/appDesktopMac.ts +470 -0
  12. package/src/commands/appIcon.ts +187 -0
  13. package/src/commands/appShared.ts +448 -0
  14. package/src/commands/appTemplates.ts +883 -0
  15. package/src/commands/appTemplatesAndroid.ts +599 -0
  16. package/src/commands/appTemplatesGradlew.ts +9 -0
  17. package/src/commands/assets.ts +28 -0
  18. package/src/commands/clone.ts +60 -0
  19. package/src/commands/compile.ts +253 -0
  20. package/src/commands/create.ts +59 -0
  21. package/src/commands/design.ts +863 -0
  22. package/src/commands/designTemplates.ts +11 -0
  23. package/src/commands/desktop.ts +122 -0
  24. package/src/commands/dev.ts +214 -0
  25. package/src/commands/diff.ts +59 -0
  26. package/src/commands/init.ts +191 -0
  27. package/src/commands/install.ts +198 -0
  28. package/src/commands/lightmap.ts +290 -0
  29. package/src/commands/link.ts +147 -0
  30. package/src/commands/login.ts +24 -0
  31. package/src/commands/navmesh.ts +225 -0
  32. package/src/commands/pn.ts +305 -0
  33. package/src/commands/projectTemplates.ts +129 -0
  34. package/src/commands/pull.ts +109 -0
  35. package/src/commands/push.ts +173 -0
  36. package/src/commands/render.ts +414 -0
  37. package/src/commands/scene.ts +148 -0
  38. package/src/commands/shaders.ts +157 -0
  39. package/src/commands/status.ts +29 -0
  40. package/src/commands/test.ts +370 -0
  41. package/src/commands/thumbs.ts +178 -0
  42. package/src/commands/types.ts +87 -0
  43. package/src/commands/update.ts +190 -0
  44. package/src/compile/assetIcons.ts +214 -0
  45. package/src/compile/collect.ts +46 -0
  46. package/src/compile/collectLocal.ts +37 -0
  47. package/src/compile/designCompile.ts +104 -0
  48. package/src/compile/fonts.ts +156 -0
  49. package/src/compile/headlessBundle.ts +129 -0
  50. package/src/compile/nativeStack.ts +21 -0
  51. package/src/compile/projectCompile.ts +109 -0
  52. package/src/compile/sceneCompile.ts +113 -0
  53. package/src/compile/screenEntry.ts +127 -0
  54. package/src/compile/shaders.ts +245 -0
  55. package/src/config.ts +42 -0
  56. package/src/designMeta.ts +35 -0
  57. package/src/desktopRenderer.ts +532 -0
  58. package/src/desktopScript.ts +276 -0
  59. package/src/dev/clientTemplates.ts +160 -0
  60. package/src/dev/devServer.ts +292 -0
  61. package/src/dev/wsServer.ts +144 -0
  62. package/src/distRoot.ts +20 -0
  63. package/src/ignore.ts +163 -0
  64. package/src/index.ts +491 -0
  65. package/src/lecodes-3d-editor.d.ts +41 -0
  66. package/src/lecodes-assets.d.ts +7 -0
  67. package/src/lecodes-design.d.ts +191 -0
  68. package/src/lecodes-renderer.d.ts +131 -0
  69. package/src/localFiles.ts +144 -0
  70. package/src/manifest.ts +46 -0
  71. package/src/matcTool.ts +137 -0
  72. package/src/peers.ts +40 -0
  73. package/src/project.ts +61 -0
  74. package/src/projectEnv.ts +94 -0
  75. package/src/qrcode-terminal.d.ts +9 -0
  76. package/src/releases.ts +125 -0
  77. package/src/serverDiff.ts +78 -0
  78. package/src/textDiff.ts +103 -0
  79. package/src/types.ts +0 -0
  80. package/src/util.ts +146 -0
  81. package/runtime/sdk/animate/animate.ts +0 -238
  82. package/runtime/sdk/animate/bezier.ts +0 -138
  83. package/runtime/sdk/animate/easings.ts +0 -126
  84. package/runtime/sdk/canvas/Canvas.ts +0 -305
  85. package/runtime/sdk/compile/aspectMacro.ts +0 -86
  86. package/runtime/sdk/compile/assetIconMacro.ts +0 -384
  87. package/runtime/sdk/compile/assetMacro.ts +0 -146
  88. package/runtime/sdk/compile/assetName.ts +0 -50
  89. package/runtime/sdk/compile/bundler.ts +0 -287
  90. package/runtime/sdk/compile/compileProject.ts +0 -129
  91. package/runtime/sdk/compile/detectEntry.ts +0 -128
  92. package/runtime/sdk/compile/fontMacro.ts +0 -459
  93. package/runtime/sdk/compile/fontRegistry.ts +0 -78
  94. package/runtime/sdk/compile/header.ts +0 -67
  95. package/runtime/sdk/compile/index.ts +0 -100
  96. package/runtime/sdk/compile/libraryImports.ts +0 -52
  97. package/runtime/sdk/compile/liteMaterial.ts +0 -247
  98. package/runtime/sdk/compile/sceneEditor.ts +0 -88
  99. package/runtime/sdk/compile/serverSplit.ts +0 -233
  100. package/runtime/sdk/compile/serverTypes.ts +0 -227
  101. package/runtime/sdk/compile/sfnt.ts +0 -98
  102. package/runtime/sdk/compile/shaderSchema.ts +0 -202
  103. package/runtime/sdk/compile/shaderTargets.ts +0 -81
  104. package/runtime/sdk/compile/sourcemap.ts +0 -25
  105. package/runtime/sdk/core/Aspect.ts +0 -568
  106. package/runtime/sdk/core/InspectorUI.ts +0 -212
  107. package/runtime/sdk/core/StateMachine.ts +0 -308
  108. package/runtime/sdk/core/color.ts +0 -66
  109. package/runtime/sdk/core/compWrite.ts +0 -42
  110. package/runtime/sdk/core/events.ts +0 -38
  111. package/runtime/sdk/core/fields.ts +0 -120
  112. package/runtime/sdk/core/registry.ts +0 -23
  113. package/runtime/sdk/core/signals.ts +0 -277
  114. package/runtime/sdk/core/time.ts +0 -84
  115. package/runtime/sdk/g2/Camera2D.ts +0 -40
  116. package/runtime/sdk/g2/CharacterController2D.ts +0 -276
  117. package/runtime/sdk/g2/Node2D.ts +0 -267
  118. package/runtime/sdk/g2/OneWay2D.ts +0 -66
  119. package/runtime/sdk/g2/Physics2D.ts +0 -346
  120. package/runtime/sdk/g2/Scene2D.ts +0 -209
  121. package/runtime/sdk/g2/Shape2D.ts +0 -259
  122. package/runtime/sdk/g2/Sprite.ts +0 -89
  123. package/runtime/sdk/g2/SpriteAnimation.ts +0 -171
  124. package/runtime/sdk/g2/SpriteSheet.ts +0 -166
  125. package/runtime/sdk/g2/Texture2D.ts +0 -47
  126. package/runtime/sdk/g2/Tilemap.ts +0 -41
  127. package/runtime/sdk/g2/Tileset.ts +0 -71
  128. package/runtime/sdk/g2/Trigger2D.ts +0 -77
  129. package/runtime/sdk/g2/autotile.ts +0 -433
  130. package/runtime/sdk/g2/cells.ts +0 -91
  131. package/runtime/sdk/g2/defineScene2d.ts +0 -381
  132. package/runtime/sdk/g2/groups2d.ts +0 -106
  133. package/runtime/sdk/g2/loop.ts +0 -50
  134. package/runtime/sdk/g2/scenarios2d.ts +0 -69
  135. package/runtime/sdk/g2/touch.ts +0 -83
  136. package/runtime/sdk/gl/Camera.ts +0 -160
  137. package/runtime/sdk/gl/CameraPlace.ts +0 -52
  138. package/runtime/sdk/gl/CharacterController.ts +0 -238
  139. package/runtime/sdk/gl/Gearbox.ts +0 -212
  140. package/runtime/sdk/gl/Geometry.ts +0 -279
  141. package/runtime/sdk/gl/IK.ts +0 -193
  142. package/runtime/sdk/gl/InstancedMesh.ts +0 -132
  143. package/runtime/sdk/gl/Light.ts +0 -107
  144. package/runtime/sdk/gl/Lightmap.ts +0 -256
  145. package/runtime/sdk/gl/Material.ts +0 -260
  146. package/runtime/sdk/gl/Mesh.ts +0 -83
  147. package/runtime/sdk/gl/Model.ts +0 -124
  148. package/runtime/sdk/gl/NavAgent.ts +0 -337
  149. package/runtime/sdk/gl/NavMesh.ts +0 -397
  150. package/runtime/sdk/gl/Node.ts +0 -350
  151. package/runtime/sdk/gl/Noise.ts +0 -30
  152. package/runtime/sdk/gl/Particles.ts +0 -676
  153. package/runtime/sdk/gl/Physics.ts +0 -222
  154. package/runtime/sdk/gl/Plane.ts +0 -53
  155. package/runtime/sdk/gl/Ragdoll.ts +0 -270
  156. package/runtime/sdk/gl/Ray.ts +0 -16
  157. package/runtime/sdk/gl/Scene.ts +0 -486
  158. package/runtime/sdk/gl/Shape.ts +0 -409
  159. package/runtime/sdk/gl/Terrain.ts +0 -1092
  160. package/runtime/sdk/gl/Texture.ts +0 -63
  161. package/runtime/sdk/gl/Trigger.ts +0 -45
  162. package/runtime/sdk/gl/Vehicle.ts +0 -473
  163. package/runtime/sdk/gl/Wheel.ts +0 -240
  164. package/runtime/sdk/gl/animation/AnimationClip.ts +0 -204
  165. package/runtime/sdk/gl/animation/Animator.ts +0 -120
  166. package/runtime/sdk/gl/animation/Layer.ts +0 -31
  167. package/runtime/sdk/gl/animation/Loop.ts +0 -30
  168. package/runtime/sdk/gl/animation/Playback.ts +0 -44
  169. package/runtime/sdk/gl/animation/core.ts +0 -336
  170. package/runtime/sdk/gl/controls.ts +0 -95
  171. package/runtime/sdk/gl/physicsEvents.ts +0 -20
  172. package/runtime/sdk/gl/scenarios.ts +0 -291
  173. package/runtime/sdk/gl/state.ts +0 -6
  174. package/runtime/sdk/gl/terrainMesh.ts +0 -219
  175. package/runtime/sdk/gl/touch.ts +0 -68
  176. package/runtime/sdk/inject.ts +0 -197
  177. package/runtime/sdk/math/Mathf.ts +0 -118
  178. package/runtime/sdk/math/mat4.ts +0 -278
  179. package/runtime/sdk/math/quat.ts +0 -232
  180. package/runtime/sdk/math/vec.ts +0 -255
  181. package/runtime/sdk/net/codec.ts +0 -119
  182. package/runtime/sdk/net/core.ts +0 -384
  183. package/runtime/sdk/net/index.ts +0 -181
  184. package/runtime/sdk/net/replication.ts +0 -622
  185. package/runtime/sdk/plugins/camera.ts +0 -81
  186. package/runtime/sdk/plugins/geolocation.ts +0 -123
  187. package/runtime/sdk/plugins/oauth.ts +0 -61
  188. package/runtime/sdk/plugins/permission.ts +0 -7
  189. package/runtime/sdk/plugins/push.ts +0 -132
  190. package/runtime/sdk/plugins/qr.ts +0 -73
  191. package/runtime/sdk/plugins/service.ts +0 -47
  192. package/runtime/sdk/runtime/app.ts +0 -101
  193. package/runtime/sdk/runtime/appEvents.ts +0 -54
  194. package/runtime/sdk/runtime/channel.ts +0 -61
  195. package/runtime/sdk/runtime/clipboard.ts +0 -20
  196. package/runtime/sdk/runtime/datetime.ts +0 -329
  197. package/runtime/sdk/runtime/device.ts +0 -293
  198. package/runtime/sdk/runtime/fetch.ts +0 -77
  199. package/runtime/sdk/runtime/files.ts +0 -23
  200. package/runtime/sdk/runtime/input.ts +0 -175
  201. package/runtime/sdk/runtime/media.ts +0 -111
  202. package/runtime/sdk/runtime/misc.ts +0 -16
  203. package/runtime/sdk/runtime/net.ts +0 -36
  204. package/runtime/sdk/runtime/rpc.ts +0 -218
  205. package/runtime/sdk/runtime/service.ts +0 -83
  206. package/runtime/sdk/runtime/share.ts +0 -9
  207. package/runtime/sdk/runtime/storage.ts +0 -13
  208. package/runtime/sdk/runtime/touch.ts +0 -76
  209. package/runtime/sdk/scene/defineScene.ts +0 -1353
  210. package/runtime/sdk/scene/editorPlugins.ts +0 -110
  211. package/runtime/sdk/scene/gizmos.ts +0 -148
  212. package/runtime/sdk/scene/grammar.ts +0 -120
  213. package/runtime/sdk/scene/material.ts +0 -188
  214. package/runtime/sdk/server/auth/appConfig.ts +0 -12
  215. package/runtime/sdk/server/auth/global.ts +0 -80
  216. package/runtime/sdk/server/auth/host.ts +0 -318
  217. package/runtime/sdk/server/auth/models.ts +0 -83
  218. package/runtime/sdk/server/auth/types.ts +0 -50
  219. package/runtime/sdk/server/channel.ts +0 -56
  220. package/runtime/sdk/server/context.ts +0 -36
  221. package/runtime/sdk/server/db/defineDb.ts +0 -237
  222. package/runtime/sdk/server/db/fields.ts +0 -132
  223. package/runtime/sdk/server/db/httpTransport.ts +0 -93
  224. package/runtime/sdk/server/db/index.ts +0 -7
  225. package/runtime/sdk/server/db/marci/query.ts +0 -412
  226. package/runtime/sdk/server/db/types.ts +0 -202
  227. package/runtime/sdk/server/errors.ts +0 -12
  228. package/runtime/sdk/server/host.ts +0 -74
  229. package/runtime/sdk/server/inject.ts +0 -13
  230. package/runtime/sdk/server/runtime.ts +0 -133
  231. package/runtime/sdk/server/validate.ts +0 -87
  232. package/runtime/sdk/ui/NativeView.ts +0 -142
  233. package/runtime/sdk/ui/UI.ts +0 -39
  234. package/runtime/sdk/ui/UIBottomSheet.ts +0 -139
  235. package/runtime/sdk/ui/UIButton.ts +0 -101
  236. package/runtime/sdk/ui/UIContainer.ts +0 -60
  237. package/runtime/sdk/ui/UIImage.ts +0 -83
  238. package/runtime/sdk/ui/UIInput.ts +0 -185
  239. package/runtime/sdk/ui/UIModal.ts +0 -139
  240. package/runtime/sdk/ui/UINode.ts +0 -830
  241. package/runtime/sdk/ui/UIPager.ts +0 -362
  242. package/runtime/sdk/ui/UIPopover.ts +0 -100
  243. package/runtime/sdk/ui/UIScreen.ts +0 -123
  244. package/runtime/sdk/ui/UIScrollable.ts +0 -87
  245. package/runtime/sdk/ui/UISpacer.ts +0 -14
  246. package/runtime/sdk/ui/UITabs.ts +0 -236
  247. package/runtime/sdk/ui/UIText.ts +0 -51
  248. package/runtime/sdk/ui/UIVideo.ts +0 -88
  249. package/runtime/sdk/ui/UIVirtualizedList.ts +0 -241
  250. package/runtime/sdk/ui/UIWidget.ts +0 -127
  251. package/runtime/sdk/ui/fonts.ts +0 -13
  252. package/runtime/sdk/ui/presentable.ts +0 -117
  253. package/runtime/sdk/ui/router.ts +0 -132
  254. package/runtime/sdk/ui/theme.ts +0 -84
@@ -1,132 +0,0 @@
1
- // One geometry drawn many times — grass tufts, crates, debris, coins, a crowd of identical props —
2
- // with a transform per instance and a handful of draw calls. Hardware instancing (Filament's
3
- // InstanceBuffer): the engine applies each instance's local matrix, so any regular material works.
4
- //
5
- // Transforms live in a CPU-side Float32Array (16 floats per instance, column-major, local to this
6
- // node). `setAt` / `hideAt` / `setMatrixAt` edit it and track the dirty range; `commit()` uploads
7
- // that range in one bridge call — call it once per frame after your edits, not per instance.
8
- // The native side splits the instances into chunks of the platform's per-draw cap (64 native,
9
- // 8 on WebGL) on child entities, and keeps a bounding box per chunk so frustum culling stays on.
10
-
11
- import { Mat4, type Mat4Like } from "../math/mat4"
12
- import { Quat, type QuatLike } from "../math/quat"
13
- import { cx, cy, cz, type Vec3Like } from "../math/vec"
14
- import type { Geometry } from "./Geometry"
15
- import { Material } from "./Material"
16
- import type { MeshOptions } from "./Mesh"
17
- import { Node } from "./Node"
18
-
19
- /** One instance's placement, local to the InstancedMesh node. Omitted fields mean identity. */
20
- export type InstanceTransform = {
21
- position?: Vec3Like
22
- /** Quaternion; wins over `eulerAngles`. */
23
- rotation?: QuatLike
24
- /** Degrees (YXZ), like `Node.eulerAngles`. */
25
- eulerAngles?: Vec3Like
26
- scale?: Vec3Like | number
27
- }
28
-
29
- const DEG = Math.PI / 180
30
-
31
- export class InstancedMesh extends Node {
32
- /** Instance capacity, fixed at construction. Unset instances are hidden. */
33
- readonly count: number
34
- private _geometry: Geometry
35
- private _material: Material
36
- private _data: Float32Array
37
- private _lo = Infinity
38
- private _hi = -1
39
-
40
- constructor(geometry: Geometry, count: number, options: MeshOptions = {}) {
41
- super()
42
- if (!(count >= 1)) throw new Error("InstancedMesh: count must be ≥ 1")
43
- this.count = Math.floor(count)
44
- this._geometry = geometry
45
- this._material = options.material ?? Material.unlit()
46
- this._data = new Float32Array(16 * this.count)
47
- _creator.createInstancedMesh(this.id, Material.idOf(this._material), geometry.vertices, geometry.normals, geometry.indices, geometry.uv, this.count)
48
- if (options.position) this.position = options.position
49
- if (options.eulerAngles) this.eulerAngles = options.eulerAngles
50
- if (options.scale !== undefined) this.scale = typeof options.scale === "number" ? [ options.scale, options.scale, options.scale ] : options.scale
51
- if (options.name !== undefined) this.name = options.name
52
- if (options.castShadows !== undefined || options.receiveShadows !== undefined) {
53
- _creator.setInstancedMeshShadows(this.id, options.castShadows ?? true, options.receiveShadows ?? true)
54
- }
55
- }
56
-
57
- override get geometry(): Geometry { return this._geometry }
58
-
59
- get material(): Material { return this._material }
60
- set material(m: Material) {
61
- this._material = m
62
- _creator.setInstancedMeshMaterial(this.id, Material.idOf(m))
63
- }
64
-
65
- /** Shadow flags for all instances (both at once — the engine sets them per draw). */
66
- setShadows(cast: boolean, receive = cast): this {
67
- _creator.setInstancedMeshShadows(this.id, cast, receive)
68
- return this
69
- }
70
-
71
- private _touch(i: number): void {
72
- if (i < this._lo) this._lo = i
73
- if (i > this._hi) this._hi = i
74
- }
75
-
76
- /** Place instance `i`. Pending until `commit()`. */
77
- setAt(i: number, t: InstanceTransform): this {
78
- if (i < 0 || i >= this.count) throw new RangeError(`InstancedMesh: index ${i} out of 0..${this.count - 1}`)
79
- const rot = t.rotation ?? (t.eulerAngles
80
- ? Quat.fromEuler(cx(t.eulerAngles) * DEG, cy(t.eulerAngles) * DEG, cz(t.eulerAngles) * DEG)
81
- : Quat.identity)
82
- const m = Mat4.compose(t.position ?? [ 0, 0, 0 ], rot, t.scale ?? 1).toArray()
83
- this._data.set(m, i * 16)
84
- this._touch(i)
85
- return this
86
- }
87
-
88
- /** Place instance `i` from a 4×4 column-major matrix (local to this node). */
89
- setMatrixAt(i: number, m: Mat4Like): this {
90
- if (i < 0 || i >= this.count) throw new RangeError(`InstancedMesh: index ${i} out of 0..${this.count - 1}`)
91
- const a = m instanceof Mat4 ? m.toArray() : m
92
- for (let k = 0; k < 16; k++) this._data[i * 16 + k] = a[k]
93
- this._touch(i)
94
- return this
95
- }
96
-
97
- /** Hide instance `i` (a zero matrix — no geometry is emitted for it). */
98
- hideAt(i: number): this {
99
- if (i < 0 || i >= this.count) throw new RangeError(`InstancedMesh: index ${i} out of 0..${this.count - 1}`)
100
- this._data.fill(0, i * 16, i * 16 + 16)
101
- this._touch(i)
102
- return this
103
- }
104
-
105
- /** Bulk write: `matrices` holds 16 floats per instance, starting at instance `offset`. */
106
- setMatrices(matrices: ArrayLike<number>, offset = 0): this {
107
- const n = Math.min(Math.floor(matrices.length / 16), this.count - offset)
108
- if (n <= 0) return this
109
- this._data.set(Array.prototype.slice.call(matrices, 0, n * 16), offset * 16)
110
- this._touch(offset)
111
- this._touch(offset + n - 1)
112
- return this
113
- }
114
-
115
- /** The 4×4 of instance `i` as stored (a copy). */
116
- matrixAt(i: number): Mat4 {
117
- return Mat4.from(this._data.subarray(i * 16, i * 16 + 16))
118
- }
119
-
120
- /** Upload every change since the last commit (one bridge call covering the dirty range). */
121
- commit(): this {
122
- if (this._hi < this._lo) return this
123
- const lo = this._lo, hi = this._hi
124
- this._lo = Infinity
125
- this._hi = -1
126
- _creator.setInstanceTransforms(this.id, this._data.subarray(lo * 16, (hi + 1) * 16), lo)
127
- return this
128
- }
129
-
130
- /** Whether edits are waiting for `commit()`. */
131
- get dirty(): boolean { return this._hi >= this._lo }
132
- }
@@ -1,107 +0,0 @@
1
- // Lights. A directional sun (the engine's primary light) and punctual point lights. Add one to a
2
- // scene like any node; `intensity` and `color` stay writable so a light can be animated.
3
-
4
- import { Color, type ColorInput } from "../core/color"
5
- import type { Vec3Like } from "../math/vec"
6
- import { Node } from "./Node"
7
-
8
- export type SunOptions = {
9
- /** Light direction (points where the light travels). Defaults to a typical key-light angle. */
10
- direction?: Vec3Like
11
- /** Luminous intensity. */
12
- intensity?: number
13
- color?: ColorInput
14
- /**
15
- * Shadow quality (default 1):
16
- * 0 - no shadows
17
- * 1 - 1024 map, hard edges (one filtered tap; the cheap level, ~3 % of a frame on an iGPU)
18
- * 2 - 1024 map, soft edges (variance shadows + blur; ~+15 % of a frame on an iGPU)
19
- * 3 - 2048 map, contact-hardening soft edges (PCSS: sharp where the caster touches, softer
20
- * away; discrete-GPU territory, ~+60 % of a frame on an iGPU)
21
- * A lightmapped level already carries every static shadow, so 0 is a legitimate choice there.
22
- */
23
- shadowsQuality?: 0 | 1 | 2 | 3
24
- /**
25
- * Metres from the camera within which the sun casts shadows (default 100). Shadows fade out by
26
- * this distance and the shadow map covers only this range, so a shorter distance is crisper for
27
- * the same quality and puts fewer casters in the shadow pass. 30-40 is plenty in first person.
28
- */
29
- shadowDistance?: number
30
- }
31
-
32
- export type PointOptions = {
33
- /**
34
- * Luminous POWER in lumens — the same physical scale as the sun's lux and `environmentIntensity`,
35
- * so a light keeps its look when the scene's exposure changes. A candle is ~12 lm, a bare 60 W
36
- * bulb ~800, a car headlight ~1500, a fireball millions. Default 1000.
37
- */
38
- intensity?: number
39
- color?: ColorInput
40
- /**
41
- * Metres of influence — past it the light contributes nothing. This is the performance knob:
42
- * overlapping point lights are the expensive case, so keep it as small as the look allows.
43
- * Default 10.
44
- */
45
- range?: number
46
- /** Point-light shadows are a cubemap render per light; off by default. */
47
- castShadows?: boolean
48
- }
49
-
50
- export class Light extends Node {
51
- private _intensity = 0
52
- /** @internal sun direction as created (the engine keeps it; Lightmap.load reads it back). */
53
- _direction: [number, number, number] = [ 0, -1, 0 ]
54
- /** The most recently created sun — what `Lightmap.load` uses unless told otherwise. */
55
- static lastSun: Light | null = null
56
-
57
- /** A directional sun light. */
58
- static sun(options: SunOptions = {}): Light {
59
- const light = new Light()
60
- const [ dx, dy, dz ] = options.direction ?? [ 0.548267, -0.473983, -0.689016 ]
61
- light._direction = [ dx, dy, dz ]
62
- light._intensity = options.intensity ?? 100000
63
- Light.lastSun = light
64
- _creator.createSunLight(
65
- light.id, dx, dy, dz,
66
- light._intensity,
67
- Color.toPackedRgb(options.color ?? 0xffffff),
68
- options.shadowsQuality ?? 1,
69
- options.shadowDistance ?? 100,
70
- )
71
- return light
72
- }
73
-
74
- /**
75
- * A point light — a lamp, a muzzle flash, a fireball. Position it like any node.
76
- *
77
- * Feature-detected: hosts that predate it create the node and light nothing, which keeps a scene
78
- * that adds atmosphere on top of its sun renderable everywhere. Check `Light.supportsPoint`
79
- * before making one carry the scene.
80
- */
81
- static point(options: PointOptions = {}): Light {
82
- const light = new Light()
83
- light._intensity = options.intensity ?? 1000
84
- _creator.createPointLight?.(
85
- light.id,
86
- light._intensity,
87
- Color.toPackedRgb(options.color ?? 0xffffff),
88
- options.range ?? 10,
89
- options.castShadows ?? false,
90
- )
91
- return light
92
- }
93
-
94
- /** Whether this host can create point lights at all. */
95
- static get supportsPoint(): boolean { return _creator.createPointLight !== undefined }
96
-
97
- /** Live intensity (sun: lux, point: lumens) — animate a flash without rebuilding the light. */
98
- get intensity(): number { return this._intensity }
99
- set intensity(value: number) {
100
- this._intensity = value
101
- _creator.setLightIntensity?.(this.id, value)
102
- }
103
-
104
- set color(value: ColorInput) {
105
- _creator.setLightColor?.(this.id, Color.toPackedRgb(value))
106
- }
107
- }
@@ -1,256 +0,0 @@
1
- // Baked shadows for static 3D geometry (docs/lightmap-plan.md). A scene FILE does all of this by
2
- // itself: `env: { lightmap: { data, texture } }` marks the level as baked, every model/mesh node
3
- // that no Physics aspect moves is a static (keys = node paths, `lightmap: true|false` overrides),
4
- // and the runtime applies the bake when the level finishes loading (scene/defineScene.ts).
5
- // Hand-built scenes register what never moves with `Lightmap.add`, bake once with
6
- // `lecodes lightmap bake` (the desktop host traces the live scene and writes lightmap.ktx2 +
7
- // lightmap.bake), and `Lightmap.load` applies the atlas at runtime:
8
- //
9
- // const crate = await Model.load(asset('./assets/props/crate.glb'), { lightmap: true })
10
- // Lightmap.add(holder) // a Model, a Mesh, or a node holding them
11
- // Lightmap.add(ground) // Mesh.plane works too
12
- // ...
13
- // await Lightmap.load(scene, { data: asset('./assets/lightmap/lightmap.bake'),
14
- // texture: asset('./assets/lightmap/lightmap.ktx2') })
15
- //
16
- // Both files are asset() handles because the compiler stages exactly the files it sees (the data
17
- // file is JSON with a .bake extension — a .json would be scanned as a code module). `load` is a
18
- // no-op when the bake is missing or the host predates the feature, so the project stays runnable —
19
- // and in BAKE mode (`lecodes lightmap bake`) it is where the bake fires: the same call marks "the
20
- // scene is complete". Atlas channels: R = sun visibility, G = ambient occlusion; the shader keeps
21
- // the real-time sun for dynamic casters and multiplies the baked mask in with an ambient floor.
22
- //
23
- // DYNAMIC objects (docs/lightmap-plan.md §9): the bake also writes lightmap.volume — the same two
24
- // channels on a 3D grid over the level. Pass it as `volume` and every model loaded with
25
- // `{ lightmap: 'dynamic' }` (the default while a scene file with `env.lightmap.volume` runs) plus
26
- // every dynamic Mesh a scene file registers samples it per pixel: a crate rolled under the roof
27
- // loses the sun like the floor does, a character near a wall picks up its ambient occlusion.
28
-
29
- import { fetch } from "../runtime/fetch"
30
- import { Texture } from "./Texture"
31
- import { Material } from "./Material"
32
- import { Mesh } from "./Mesh"
33
- import { Model } from "./Model"
34
- import { Node } from "./Node"
35
- import type { Scene } from "./Scene"
36
-
37
- export type LightmapLoadOptions = {
38
- /** 1 = baked sun shadows at full strength, 0 = ambient occlusion only. Default 1. */
39
- sunStrength?: number
40
- /** Multiplier on the ambient (IBL) share in the shadow math — how bright a fully shadowed texel
41
- * stays. 1 reproduces filament's own real-time shadow darkness (the shader reads the scene's sun
42
- * and IBL from filament's per-frame uniforms); raise it for lighter shadows, lower for deeper. Default 1. */
43
- ambientScale?: number
44
- }
45
-
46
- export type LightmapInfo = {
47
- size: number
48
- texel: number
49
- /** The light volume, when one was loaded: grid dims + cell size in metres. */
50
- volume?: { dims: number[], cell: number }
51
- /** keys applied / keys in the file / registered statics without a rect */
52
- applied: number
53
- total: number
54
- missing: string[]
55
- }
56
-
57
- import type { Terrain } from "./Terrain"
58
-
59
- type Entry = { key: string, model?: Model, mesh?: Mesh, terrain?: Terrain }
60
-
61
- const terrainOf = (n: Node): Terrain | null => (n as unknown as { _terrain?: Terrain })._terrain ?? null
62
-
63
- type BakeConfig = {
64
- outDir: string, size?: number, texel?: number, sunRays?: number, aoRays?: number,
65
- aoDistance?: number, bias?: number, sunAngle?: number, volumeCell?: number,
66
- }
67
-
68
- type VolumeHeader = { texture: number, dims: number[], min: number[], cell: number }
69
-
70
- const bakeConfig = (): BakeConfig | null => {
71
- const g = (globalThis as unknown as { __lecodesLightmap?: BakeConfig }).__lecodesLightmap
72
- return g && typeof g.outDir === "string" ? g : null
73
- }
74
-
75
- /** A hex colour as the 9-char form the float4 uniform path expects ("#rrggbbaa"). */
76
- const hex8 = (c: unknown): string => {
77
- if (typeof c !== "string") return "#ffffffff"
78
- if (c.length === 7) return c + "ff"
79
- if (c.length === 4) return "#" + c[1] + c[1] + c[2] + c[2] + c[3] + c[3] + "ff"
80
- return c
81
- }
82
-
83
- export class Lightmap {
84
- /** True while `lecodes lightmap bake` runs the app — skip menus and build the scene straight away. */
85
- static get baking(): boolean { return bakeConfig() !== null }
86
-
87
- private static entries: Entry[] = []
88
- private static dynamics: Mesh[] = []
89
- private static ordinals = new Map<string, number>()
90
- private static warned = false
91
-
92
-
93
- /** Register static geometry — a Model, a Mesh, or any node whose subtree holds them. Statics are
94
- * both receivers and occluders in the bake. `key` names the entry in lightmap.bake (default:
95
- * the node's name + a running number, `container#3`); pass one when names are not stable. */
96
- static add(node: Node, key?: string): Node {
97
- const base = key ?? `${node.name || "node"}#${Lightmap.next(node.name || "node")}`
98
- const found: Entry[] = []
99
- const walk = (n: Node): void => {
100
- const t = terrainOf(n)
101
- if (t) { found.push({ key: base, terrain: t }); return } // one receiver, its chunks are internal
102
- if (n instanceof Model) { found.push({ key: base, model: n }); return }
103
- if (n instanceof Mesh) { found.push({ key: base, mesh: n }); return }
104
- for (const c of n.children) walk(c)
105
- }
106
- walk(node)
107
- if (found.length === 0) { console.warn(`Lightmap.add: ${base} holds no Model or Mesh`); return node }
108
- if (found.length > 1) found.forEach((e, i) => { e.key = `${base}/${i}` })
109
- Lightmap.entries.push(...found)
110
- return node
111
- }
112
-
113
- /** @internal Scene-file runtime: one model/mesh node under its path key (no subtree walk). */
114
- static _register(node: Model | Mesh, key: string): void {
115
- Lightmap.entries.push(node instanceof Model ? { key, model: node } : { key, mesh: node })
116
- }
117
-
118
- /** @internal Scene-file runtime: a terrain node (its own material carries the atlas, no swap). */
119
- static _registerTerrain(terrain: Terrain, key: string): void {
120
- Lightmap.entries.push({ key, terrain })
121
- }
122
-
123
- /** @internal Scene-file runtime: a dynamic Mesh — swapped to the lightmap material on the volume
124
- * when `load` runs (dynamic Models need nothing here: the engine binds the volume itself). */
125
- static _registerDynamic(mesh: Mesh): void {
126
- Lightmap.dynamics.push(mesh)
127
- }
128
-
129
- private static next(name: string): number {
130
- const n = Lightmap.ordinals.get(name) ?? 0
131
- Lightmap.ordinals.set(name, n + 1)
132
- return n
133
- }
134
-
135
- /** Forget every registration (a scene rebuild). */
136
- static clear(): void {
137
- Lightmap.entries = []
138
- Lightmap.dynamics = []
139
- Lightmap.ordinals.clear()
140
- Model._lightmapDefault = false
141
- _creator.lightmapVolumeSet?.(0xFFFFFFFF, 0, 0, 0, 0, 0, 0, 0, 1, 1)
142
- }
143
-
144
- /** Apply a bake — or, under `lecodes lightmap bake`, run it. Resolves to null when nothing was
145
- * applied (no bake yet, a host without the feature, bake mode). */
146
- static async load(scene: Scene, files: { data: string, texture: string, volume?: string }, options: LightmapLoadOptions = {}): Promise<LightmapInfo | null> {
147
- const bake = bakeConfig()
148
- if (bake) { Lightmap.bake(bake); return null }
149
- if (!_creator.lightmapApply) {
150
- if (!Lightmap.warned) { Lightmap.warned = true; console.warn("Lightmap: this host has no lightmap support — rendering without the bake") }
151
- return null
152
- }
153
- let data: { size: number, texel: number, sun?: number[] | null, instances: { key: string, receiver: boolean, st?: number[] }[] }
154
- try {
155
- const resp = await fetch(files.data, { useOnce: true })
156
- if (resp.status >= 400) { console.warn(`Lightmap: no bake at ${files.data} (HTTP ${resp.status}) — run \`lecodes lightmap bake\``); return null }
157
- data = resp.json()
158
- resp.dispose()
159
- } catch (e) {
160
- console.warn(`Lightmap: could not read ${files.data}: ${String(e)}`)
161
- return null
162
- }
163
- const texture = await Texture.load(files.texture)
164
- const ambientScale = options.ambientScale ?? 1
165
- const strength = options.sunStrength ?? 1
166
- // the light volume first: it claims every lightmap-material instance, the atlas rects below win back the statics
167
- const volume = files.volume ? await Lightmap.loadVolume(files.volume, strength, ambientScale) : null
168
- const rects = new Map<string, number[]>()
169
- for (const inst of data.instances) if (inst.receiver && inst.st) rects.set(inst.key, inst.st)
170
- let applied = 0
171
- const missing: string[] = []
172
- for (const e of Lightmap.entries) {
173
- const st = rects.get(e.key)
174
- if (!st) { missing.push(e.key); continue }
175
- if (e.model) {
176
- const n = _creator.lightmapApply(e.model.id, texture._id, st[0], st[1], st[2], st[3], ambientScale, strength)
177
- if (n === 0) { console.warn(`Lightmap: ${e.key} was not loaded with { lightmap: true } — skipped`); continue }
178
- e.model.castShadows = false
179
- } else if (e.mesh) {
180
- Lightmap.swapMeshMaterial(e.mesh, texture, st, ambientScale, strength)
181
- e.mesh.castShadows = false
182
- } else if (e.terrain) {
183
- // terrain.mat carries lightmap.mat's atlas block: bind the rect on the terrain's own material
184
- e.terrain.material.set("lightmap", texture).set("lightmapST", [ st[0], st[1], st[2], st[3] ])
185
- .set("ambientScale", ambientScale).set("sunStrength", strength)
186
- e.terrain.castShadows = false
187
- }
188
- applied++
189
- }
190
- if (missing.length) console.warn(`Lightmap: ${missing.length} static(s) have no rect in the bake (${missing.slice(0, 5).join(", ")}${missing.length > 5 ? "…" : ""}) — rebake`)
191
- const info: LightmapInfo = { size: data.size, texel: data.texel, applied, total: rects.size, missing }
192
- if (volume) info.volume = { dims: volume.dims, cell: volume.cell }
193
- return info
194
- }
195
-
196
- /** lightmap.volume → the engine's 3D texture → THE volume for every dynamic lightmap-material instance,
197
- * plus the registered dynamic Meshes. Null when the file is a placeholder or the host lacks the feature. */
198
- private static async loadVolume(url: string, strength: number, ambientScale: number): Promise<VolumeHeader | null> {
199
- if (!_creator.lightmapVolumeLoad || !_creator.lightmapVolumeSet) return null
200
- let header: VolumeHeader
201
- try {
202
- const resp = await fetch(url, { useOnce: true })
203
- if (resp.status >= 400) { console.warn(`Lightmap: no volume at ${url} (HTTP ${resp.status}) — run \`lecodes lightmap bake\``); return null }
204
- const json = _creator.lightmapVolumeLoad((resp as unknown as { _id: number })._id)
205
- resp.dispose()
206
- if (!json) { console.warn(`Lightmap: ${url} is not a light volume — rebake`); return null }
207
- header = JSON.parse(json) as VolumeHeader
208
- } catch (e) {
209
- console.warn(`Lightmap: could not read ${url}: ${String(e)}`)
210
- return null
211
- }
212
- const [ nx, ny, nz ] = header.dims
213
- if (nx * ny * nz <= 1) return null // the CLI's placeholder: nothing baked yet
214
- const size = [ nx * header.cell, ny * header.cell, nz * header.cell ]
215
- const texture = new Texture(nx, ny, header.texture)
216
- _creator.lightmapVolumeSet(header.texture, header.min[0], header.min[1], header.min[2], size[0], size[1], size[2], header.cell, strength, ambientScale)
217
- for (const mesh of Lightmap.dynamics) {
218
- const m = Lightmap.meshMaterial(mesh, ambientScale, strength)
219
- m.set("probeVolume", texture)
220
- m.set("probeMin", [ header.min[0], header.min[1], header.min[2], header.cell * 0.5 ])
221
- m.set("probeInvSize", [ 1 / size[0], 1 / size[1], 1 / size[2], 1 ])
222
- mesh.setMaterial(m)
223
- }
224
- return header
225
- }
226
-
227
- /** A Mesh keeps its look (colour / map / roughness / metallic) but moves to the lightmap material. */
228
- private static meshMaterial(mesh: Mesh, ambientScale: number, strength: number): Material {
229
- const src = mesh.material
230
- const u = src?.uniforms ?? {}
231
- const m = Material.lightmap()
232
- m.set("baseColorFactor", hex8(u.baseColor))
233
- if (u.baseColorMap instanceof Texture) m.set("baseColorMap", u.baseColorMap)
234
- if (typeof u.roughness === "number") m.set("roughnessFactor", u.roughness)
235
- if (typeof u.metallic === "number") m.set("metallicFactor", u.metallic)
236
- m.set("ambientScale", ambientScale)
237
- m.set("sunStrength", strength)
238
- return m
239
- }
240
-
241
- private static swapMeshMaterial(mesh: Mesh, texture: Texture, st: number[], ambientScale: number, strength: number): void {
242
- const m = Lightmap.meshMaterial(mesh, ambientScale, strength)
243
- m.set("lightmap", texture)
244
- m.set("lightmapST", [ st[0], st[1], st[2], st[3] ])
245
- mesh.setMaterial(m)
246
- }
247
-
248
- private static bake(cfg: BakeConfig): void {
249
- if (!_creator.lightmapBake) { console.error("[lightmap] error: this host has no bake module (build the desktop host with CREATOR_GL_LIGHTMAP, or update lecodes desktop)"); return }
250
- const ids = Uint32Array.from(Lightmap.entries.map((e) => (e.model ?? e.mesh ?? e.terrain!.node).id))
251
- const keys = Lightmap.entries.map((e) => e.key).join("\n")
252
- if (ids.length === 0) { console.error("[lightmap] error: nothing registered — call Lightmap.add on the static props before Lightmap.load"); return }
253
- _creator.lightmapBake(cfg.outDir, ids, keys, cfg.size ?? 4096, cfg.texel ?? 0.02, cfg.sunRays ?? 32, cfg.aoRays ?? 64,
254
- cfg.aoDistance ?? 2, cfg.bias ?? 0.02, cfg.sunAngle ?? 0.5, cfg.volumeCell ?? 0.5)
255
- }
256
- }