lecodes-cli 0.18.2 → 0.19.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (224) hide show
  1. package/dist/index.js +1866 -301
  2. package/package.json +14 -12
  3. package/runtime/scene-harness.json +1 -1
  4. package/runtime/sdk-types.json +1 -1
  5. package/src/api.ts +302 -0
  6. package/src/browserAuth.ts +87 -0
  7. package/src/cmgenTool.ts +104 -0
  8. package/src/commands/app.ts +891 -0
  9. package/src/commands/appAndroid.ts +603 -0
  10. package/src/commands/appDesktop.ts +326 -0
  11. package/src/commands/appDesktopMac.ts +470 -0
  12. package/src/commands/appIcon.ts +187 -0
  13. package/src/commands/appShared.ts +448 -0
  14. package/src/commands/appTemplates.ts +883 -0
  15. package/src/commands/appTemplatesAndroid.ts +599 -0
  16. package/src/commands/appTemplatesGradlew.ts +9 -0
  17. package/src/commands/assets.ts +28 -0
  18. package/src/commands/clone.ts +60 -0
  19. package/src/commands/compile.ts +253 -0
  20. package/src/commands/create.ts +59 -0
  21. package/src/commands/design.ts +863 -0
  22. package/src/commands/designTemplates.ts +11 -0
  23. package/src/commands/desktop.ts +122 -0
  24. package/src/commands/dev.ts +214 -0
  25. package/src/commands/diff.ts +59 -0
  26. package/src/commands/init.ts +191 -0
  27. package/src/commands/install.ts +198 -0
  28. package/src/commands/lightmap.ts +290 -0
  29. package/src/commands/link.ts +147 -0
  30. package/src/commands/login.ts +24 -0
  31. package/src/commands/navmesh.ts +225 -0
  32. package/src/commands/pn.ts +305 -0
  33. package/src/commands/projectTemplates.ts +129 -0
  34. package/src/commands/pull.ts +109 -0
  35. package/src/commands/push.ts +173 -0
  36. package/src/commands/render.ts +414 -0
  37. package/src/commands/scene.ts +148 -0
  38. package/src/commands/shaders.ts +157 -0
  39. package/src/commands/status.ts +29 -0
  40. package/src/commands/test.ts +370 -0
  41. package/src/commands/thumbs.ts +178 -0
  42. package/src/commands/types.ts +87 -0
  43. package/src/commands/update.ts +190 -0
  44. package/src/compile/assetIcons.ts +214 -0
  45. package/src/compile/collect.ts +46 -0
  46. package/src/compile/collectLocal.ts +37 -0
  47. package/src/compile/designCompile.ts +104 -0
  48. package/src/compile/fonts.ts +156 -0
  49. package/src/compile/headlessBundle.ts +129 -0
  50. package/src/compile/nativeStack.ts +21 -0
  51. package/src/compile/projectCompile.ts +109 -0
  52. package/src/compile/sceneCompile.ts +113 -0
  53. package/src/compile/screenEntry.ts +127 -0
  54. package/src/compile/shaders.ts +245 -0
  55. package/src/config.ts +42 -0
  56. package/src/designMeta.ts +35 -0
  57. package/src/desktopRenderer.ts +532 -0
  58. package/src/desktopScript.ts +276 -0
  59. package/src/dev/clientTemplates.ts +160 -0
  60. package/src/dev/devServer.ts +292 -0
  61. package/src/dev/wsServer.ts +144 -0
  62. package/src/distRoot.ts +20 -0
  63. package/src/ignore.ts +163 -0
  64. package/src/index.ts +491 -0
  65. package/src/lecodes-3d-editor.d.ts +41 -0
  66. package/src/lecodes-assets.d.ts +7 -0
  67. package/src/lecodes-design.d.ts +191 -0
  68. package/src/lecodes-renderer.d.ts +131 -0
  69. package/src/localFiles.ts +144 -0
  70. package/src/manifest.ts +46 -0
  71. package/src/matcTool.ts +137 -0
  72. package/src/peers.ts +40 -0
  73. package/src/project.ts +61 -0
  74. package/src/projectEnv.ts +94 -0
  75. package/src/qrcode-terminal.d.ts +9 -0
  76. package/src/releases.ts +125 -0
  77. package/src/serverDiff.ts +78 -0
  78. package/src/textDiff.ts +103 -0
  79. package/src/types.ts +0 -0
  80. package/src/util.ts +146 -0
  81. package/runtime/sdk/animate/animate.ts +0 -238
  82. package/runtime/sdk/animate/bezier.ts +0 -138
  83. package/runtime/sdk/animate/easings.ts +0 -126
  84. package/runtime/sdk/canvas/Canvas.ts +0 -305
  85. package/runtime/sdk/core/Aspect.ts +0 -512
  86. package/runtime/sdk/core/InspectorUI.ts +0 -212
  87. package/runtime/sdk/core/color.ts +0 -66
  88. package/runtime/sdk/core/compWrite.ts +0 -42
  89. package/runtime/sdk/core/events.ts +0 -38
  90. package/runtime/sdk/core/fields.ts +0 -120
  91. package/runtime/sdk/core/registry.ts +0 -23
  92. package/runtime/sdk/core/signals.ts +0 -277
  93. package/runtime/sdk/core/time.ts +0 -81
  94. package/runtime/sdk/g2/Camera2D.ts +0 -40
  95. package/runtime/sdk/g2/CharacterController2D.ts +0 -276
  96. package/runtime/sdk/g2/Node2D.ts +0 -267
  97. package/runtime/sdk/g2/OneWay2D.ts +0 -66
  98. package/runtime/sdk/g2/Physics2D.ts +0 -346
  99. package/runtime/sdk/g2/Scene2D.ts +0 -209
  100. package/runtime/sdk/g2/Shape2D.ts +0 -259
  101. package/runtime/sdk/g2/Sprite.ts +0 -89
  102. package/runtime/sdk/g2/SpriteAnimation.ts +0 -171
  103. package/runtime/sdk/g2/SpriteSheet.ts +0 -166
  104. package/runtime/sdk/g2/Texture2D.ts +0 -47
  105. package/runtime/sdk/g2/Tilemap.ts +0 -41
  106. package/runtime/sdk/g2/Tileset.ts +0 -71
  107. package/runtime/sdk/g2/Trigger2D.ts +0 -77
  108. package/runtime/sdk/g2/autotile.ts +0 -433
  109. package/runtime/sdk/g2/cells.ts +0 -91
  110. package/runtime/sdk/g2/defineScene2d.ts +0 -381
  111. package/runtime/sdk/g2/groups2d.ts +0 -106
  112. package/runtime/sdk/g2/loop.ts +0 -50
  113. package/runtime/sdk/g2/scenarios2d.ts +0 -69
  114. package/runtime/sdk/g2/touch.ts +0 -83
  115. package/runtime/sdk/gl/Camera.ts +0 -160
  116. package/runtime/sdk/gl/CameraPlace.ts +0 -52
  117. package/runtime/sdk/gl/CharacterController.ts +0 -238
  118. package/runtime/sdk/gl/Gearbox.ts +0 -212
  119. package/runtime/sdk/gl/Geometry.ts +0 -279
  120. package/runtime/sdk/gl/IK.ts +0 -193
  121. package/runtime/sdk/gl/InstancedMesh.ts +0 -132
  122. package/runtime/sdk/gl/Light.ts +0 -99
  123. package/runtime/sdk/gl/Lightmap.ts +0 -179
  124. package/runtime/sdk/gl/Material.ts +0 -245
  125. package/runtime/sdk/gl/Mesh.ts +0 -83
  126. package/runtime/sdk/gl/Model.ts +0 -64
  127. package/runtime/sdk/gl/Node.ts +0 -350
  128. package/runtime/sdk/gl/Noise.ts +0 -30
  129. package/runtime/sdk/gl/Particles.ts +0 -676
  130. package/runtime/sdk/gl/Physics.ts +0 -222
  131. package/runtime/sdk/gl/Plane.ts +0 -53
  132. package/runtime/sdk/gl/Ray.ts +0 -16
  133. package/runtime/sdk/gl/Scene.ts +0 -479
  134. package/runtime/sdk/gl/Shape.ts +0 -377
  135. package/runtime/sdk/gl/Texture.ts +0 -46
  136. package/runtime/sdk/gl/Trigger.ts +0 -45
  137. package/runtime/sdk/gl/Vehicle.ts +0 -473
  138. package/runtime/sdk/gl/Wheel.ts +0 -240
  139. package/runtime/sdk/gl/animation/AnimationClip.ts +0 -204
  140. package/runtime/sdk/gl/animation/Animator.ts +0 -87
  141. package/runtime/sdk/gl/animation/Layer.ts +0 -29
  142. package/runtime/sdk/gl/animation/Loop.ts +0 -25
  143. package/runtime/sdk/gl/animation/Playback.ts +0 -43
  144. package/runtime/sdk/gl/animation/core.ts +0 -294
  145. package/runtime/sdk/gl/controls.ts +0 -95
  146. package/runtime/sdk/gl/physicsEvents.ts +0 -20
  147. package/runtime/sdk/gl/scenarios.ts +0 -291
  148. package/runtime/sdk/gl/state.ts +0 -6
  149. package/runtime/sdk/gl/touch.ts +0 -68
  150. package/runtime/sdk/inject.ts +0 -186
  151. package/runtime/sdk/math/Mathf.ts +0 -118
  152. package/runtime/sdk/math/mat4.ts +0 -278
  153. package/runtime/sdk/math/quat.ts +0 -232
  154. package/runtime/sdk/math/vec.ts +0 -255
  155. package/runtime/sdk/plugins/camera.ts +0 -81
  156. package/runtime/sdk/plugins/geolocation.ts +0 -123
  157. package/runtime/sdk/plugins/oauth.ts +0 -61
  158. package/runtime/sdk/plugins/permission.ts +0 -7
  159. package/runtime/sdk/plugins/push.ts +0 -132
  160. package/runtime/sdk/plugins/qr.ts +0 -73
  161. package/runtime/sdk/plugins/service.ts +0 -47
  162. package/runtime/sdk/runtime/app.ts +0 -101
  163. package/runtime/sdk/runtime/appEvents.ts +0 -54
  164. package/runtime/sdk/runtime/channel.ts +0 -61
  165. package/runtime/sdk/runtime/clipboard.ts +0 -20
  166. package/runtime/sdk/runtime/datetime.ts +0 -329
  167. package/runtime/sdk/runtime/device.ts +0 -293
  168. package/runtime/sdk/runtime/fetch.ts +0 -77
  169. package/runtime/sdk/runtime/files.ts +0 -23
  170. package/runtime/sdk/runtime/input.ts +0 -175
  171. package/runtime/sdk/runtime/media.ts +0 -111
  172. package/runtime/sdk/runtime/misc.ts +0 -16
  173. package/runtime/sdk/runtime/net.ts +0 -36
  174. package/runtime/sdk/runtime/rpc.ts +0 -218
  175. package/runtime/sdk/runtime/service.ts +0 -83
  176. package/runtime/sdk/runtime/share.ts +0 -9
  177. package/runtime/sdk/runtime/storage.ts +0 -13
  178. package/runtime/sdk/runtime/touch.ts +0 -76
  179. package/runtime/sdk/scene/defineScene.ts +0 -1227
  180. package/runtime/sdk/scene/editorPlugins.ts +0 -92
  181. package/runtime/sdk/scene/gizmos.ts +0 -148
  182. package/runtime/sdk/scene/grammar.ts +0 -120
  183. package/runtime/sdk/scene/material.ts +0 -188
  184. package/runtime/sdk/server/auth/appConfig.ts +0 -12
  185. package/runtime/sdk/server/auth/global.ts +0 -80
  186. package/runtime/sdk/server/auth/host.ts +0 -318
  187. package/runtime/sdk/server/auth/models.ts +0 -83
  188. package/runtime/sdk/server/auth/types.ts +0 -50
  189. package/runtime/sdk/server/channel.ts +0 -56
  190. package/runtime/sdk/server/context.ts +0 -36
  191. package/runtime/sdk/server/db/defineDb.ts +0 -237
  192. package/runtime/sdk/server/db/fields.ts +0 -132
  193. package/runtime/sdk/server/db/httpTransport.ts +0 -93
  194. package/runtime/sdk/server/db/index.ts +0 -7
  195. package/runtime/sdk/server/db/marci/query.ts +0 -412
  196. package/runtime/sdk/server/db/types.ts +0 -202
  197. package/runtime/sdk/server/errors.ts +0 -12
  198. package/runtime/sdk/server/host.ts +0 -74
  199. package/runtime/sdk/server/inject.ts +0 -13
  200. package/runtime/sdk/server/runtime.ts +0 -133
  201. package/runtime/sdk/server/validate.ts +0 -87
  202. package/runtime/sdk/ui/NativeView.ts +0 -142
  203. package/runtime/sdk/ui/UI.ts +0 -39
  204. package/runtime/sdk/ui/UIBottomSheet.ts +0 -139
  205. package/runtime/sdk/ui/UIButton.ts +0 -101
  206. package/runtime/sdk/ui/UIContainer.ts +0 -60
  207. package/runtime/sdk/ui/UIImage.ts +0 -83
  208. package/runtime/sdk/ui/UIInput.ts +0 -185
  209. package/runtime/sdk/ui/UIModal.ts +0 -139
  210. package/runtime/sdk/ui/UINode.ts +0 -826
  211. package/runtime/sdk/ui/UIPager.ts +0 -362
  212. package/runtime/sdk/ui/UIPopover.ts +0 -100
  213. package/runtime/sdk/ui/UIScreen.ts +0 -123
  214. package/runtime/sdk/ui/UIScrollable.ts +0 -87
  215. package/runtime/sdk/ui/UISpacer.ts +0 -14
  216. package/runtime/sdk/ui/UITabs.ts +0 -236
  217. package/runtime/sdk/ui/UIText.ts +0 -51
  218. package/runtime/sdk/ui/UIVideo.ts +0 -88
  219. package/runtime/sdk/ui/UIVirtualizedList.ts +0 -241
  220. package/runtime/sdk/ui/UIWidget.ts +0 -127
  221. package/runtime/sdk/ui/fonts.ts +0 -13
  222. package/runtime/sdk/ui/presentable.ts +0 -117
  223. package/runtime/sdk/ui/router.ts +0 -132
  224. package/runtime/sdk/ui/theme.ts +0 -84
@@ -1,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,99 +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 - standard (1024 shadow map, hard PCF edges)
18
- * 2 - high (2048 + stable, soft contact-hardening edges)
19
- * 3 - best (4096 + stable, PCSS soft shadows)
20
- * Higher values cost more GPU/memory.
21
- */
22
- shadowsQuality?: 0 | 1 | 2 | 3
23
- }
24
-
25
- export type PointOptions = {
26
- /**
27
- * Luminous POWER in lumens — the same physical scale as the sun's lux and `environmentIntensity`,
28
- * so a light keeps its look when the scene's exposure changes. A candle is ~12 lm, a bare 60 W
29
- * bulb ~800, a car headlight ~1500, a fireball millions. Default 1000.
30
- */
31
- intensity?: number
32
- color?: ColorInput
33
- /**
34
- * Metres of influence — past it the light contributes nothing. This is the performance knob:
35
- * overlapping point lights are the expensive case, so keep it as small as the look allows.
36
- * Default 10.
37
- */
38
- range?: number
39
- /** Point-light shadows are a cubemap render per light; off by default. */
40
- castShadows?: boolean
41
- }
42
-
43
- export class Light extends Node {
44
- private _intensity = 0
45
- /** @internal sun direction as created (the engine keeps it; Lightmap.load reads it back). */
46
- _direction: [number, number, number] = [ 0, -1, 0 ]
47
- /** The most recently created sun — what `Lightmap.load` uses unless told otherwise. */
48
- static lastSun: Light | null = null
49
-
50
- /** A directional sun light. */
51
- static sun(options: SunOptions = {}): Light {
52
- const light = new Light()
53
- const [ dx, dy, dz ] = options.direction ?? [ 0.548267, -0.473983, -0.689016 ]
54
- light._direction = [ dx, dy, dz ]
55
- light._intensity = options.intensity ?? 100000
56
- Light.lastSun = light
57
- _creator.createSunLight(
58
- light.id, dx, dy, dz,
59
- light._intensity,
60
- Color.toPackedRgb(options.color ?? 0xffffff),
61
- options.shadowsQuality ?? 1,
62
- )
63
- return light
64
- }
65
-
66
- /**
67
- * A point light — a lamp, a muzzle flash, a fireball. Position it like any node.
68
- *
69
- * Feature-detected: hosts that predate it create the node and light nothing, which keeps a scene
70
- * that adds atmosphere on top of its sun renderable everywhere. Check `Light.supportsPoint`
71
- * before making one carry the scene.
72
- */
73
- static point(options: PointOptions = {}): Light {
74
- const light = new Light()
75
- light._intensity = options.intensity ?? 1000
76
- _creator.createPointLight?.(
77
- light.id,
78
- light._intensity,
79
- Color.toPackedRgb(options.color ?? 0xffffff),
80
- options.range ?? 10,
81
- options.castShadows ?? false,
82
- )
83
- return light
84
- }
85
-
86
- /** Whether this host can create point lights at all. */
87
- static get supportsPoint(): boolean { return _creator.createPointLight !== undefined }
88
-
89
- /** Live intensity (sun: lux, point: lumens) — animate a flash without rebuilding the light. */
90
- get intensity(): number { return this._intensity }
91
- set intensity(value: number) {
92
- this._intensity = value
93
- _creator.setLightIntensity?.(this.id, value)
94
- }
95
-
96
- set color(value: ColorInput) {
97
- _creator.setLightColor?.(this.id, Color.toPackedRgb(value))
98
- }
99
- }
@@ -1,179 +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
- import { fetch } from "../runtime/fetch"
24
- import { Texture } from "./Texture"
25
- import { Material } from "./Material"
26
- import { Mesh } from "./Mesh"
27
- import { Model } from "./Model"
28
- import { Node } from "./Node"
29
- import type { Scene } from "./Scene"
30
-
31
- export type LightmapLoadOptions = {
32
- /** 1 = baked sun shadows at full strength, 0 = ambient occlusion only. Default 1. */
33
- sunStrength?: number
34
- /** Multiplier on the ambient (IBL) share in the shadow math — how bright a fully shadowed texel
35
- * stays. 1 reproduces filament's own real-time shadow darkness (the shader reads the scene's sun
36
- * and IBL from filament's per-frame uniforms); raise it for lighter shadows, lower for deeper. Default 1. */
37
- ambientScale?: number
38
- }
39
-
40
- export type LightmapInfo = {
41
- size: number
42
- texel: number
43
- /** keys applied / keys in the file / registered statics without a rect */
44
- applied: number
45
- total: number
46
- missing: string[]
47
- }
48
-
49
- type Entry = { key: string, model?: Model, mesh?: Mesh }
50
-
51
- type BakeConfig = {
52
- outDir: string, size?: number, texel?: number, sunRays?: number, aoRays?: number,
53
- aoDistance?: number, bias?: number, sunAngle?: number,
54
- }
55
-
56
- const bakeConfig = (): BakeConfig | null => {
57
- const g = (globalThis as unknown as { __lecodesLightmap?: BakeConfig }).__lecodesLightmap
58
- return g && typeof g.outDir === "string" ? g : null
59
- }
60
-
61
- /** A hex colour as the 9-char form the float4 uniform path expects ("#rrggbbaa"). */
62
- const hex8 = (c: unknown): string => {
63
- if (typeof c !== "string") return "#ffffffff"
64
- if (c.length === 7) return c + "ff"
65
- if (c.length === 4) return "#" + c[1] + c[1] + c[2] + c[2] + c[3] + c[3] + "ff"
66
- return c
67
- }
68
-
69
- export class Lightmap {
70
- /** True while `lecodes lightmap bake` runs the app — skip menus and build the scene straight away. */
71
- static get baking(): boolean { return bakeConfig() !== null }
72
-
73
- private static entries: Entry[] = []
74
- private static ordinals = new Map<string, number>()
75
- private static warned = false
76
-
77
- /** Register static geometry — a Model, a Mesh, or any node whose subtree holds them. Statics are
78
- * both receivers and occluders in the bake. `key` names the entry in lightmap.bake (default:
79
- * the node's name + a running number, `container#3`); pass one when names are not stable. */
80
- static add(node: Node, key?: string): Node {
81
- const base = key ?? `${node.name || "node"}#${Lightmap.next(node.name || "node")}`
82
- const found: Entry[] = []
83
- const walk = (n: Node): void => {
84
- if (n instanceof Model) { found.push({ key: base, model: n }); return }
85
- if (n instanceof Mesh) { found.push({ key: base, mesh: n }); return }
86
- for (const c of n.children) walk(c)
87
- }
88
- walk(node)
89
- if (found.length === 0) { console.warn(`Lightmap.add: ${base} holds no Model or Mesh`); return node }
90
- if (found.length > 1) found.forEach((e, i) => { e.key = `${base}/${i}` })
91
- Lightmap.entries.push(...found)
92
- return node
93
- }
94
-
95
- /** @internal Scene-file runtime: one model/mesh node under its path key (no subtree walk). */
96
- static _register(node: Model | Mesh, key: string): void {
97
- Lightmap.entries.push(node instanceof Model ? { key, model: node } : { key, mesh: node })
98
- }
99
-
100
- private static next(name: string): number {
101
- const n = Lightmap.ordinals.get(name) ?? 0
102
- Lightmap.ordinals.set(name, n + 1)
103
- return n
104
- }
105
-
106
- /** Forget every registration (a scene rebuild). */
107
- static clear(): void {
108
- Lightmap.entries = []
109
- Lightmap.ordinals.clear()
110
- }
111
-
112
- /** Apply a bake — or, under `lecodes lightmap bake`, run it. Resolves to null when nothing was
113
- * applied (no bake yet, a host without the feature, bake mode). */
114
- static async load(scene: Scene, files: { data: string, texture: string }, options: LightmapLoadOptions = {}): Promise<LightmapInfo | null> {
115
- const bake = bakeConfig()
116
- if (bake) { Lightmap.bake(bake); return null }
117
- if (!_creator.lightmapApply) {
118
- if (!Lightmap.warned) { Lightmap.warned = true; console.warn("Lightmap: this host has no lightmap support — rendering without the bake") }
119
- return null
120
- }
121
- let data: { size: number, texel: number, sun?: number[] | null, instances: { key: string, receiver: boolean, st?: number[] }[] }
122
- try {
123
- const resp = await fetch(files.data, { useOnce: true })
124
- if (resp.status >= 400) { console.warn(`Lightmap: no bake at ${files.data} (HTTP ${resp.status}) — run \`lecodes lightmap bake\``); return null }
125
- data = resp.json()
126
- resp.dispose()
127
- } catch (e) {
128
- console.warn(`Lightmap: could not read ${files.data}: ${String(e)}`)
129
- return null
130
- }
131
- const texture = await Texture.load(files.texture)
132
- const ambientScale = options.ambientScale ?? 1
133
- const strength = options.sunStrength ?? 1
134
- const rects = new Map<string, number[]>()
135
- for (const inst of data.instances) if (inst.receiver && inst.st) rects.set(inst.key, inst.st)
136
- let applied = 0
137
- const missing: string[] = []
138
- for (const e of Lightmap.entries) {
139
- const st = rects.get(e.key)
140
- if (!st) { missing.push(e.key); continue }
141
- if (e.model) {
142
- const n = _creator.lightmapApply(e.model.id, texture._id, st[0], st[1], st[2], st[3], ambientScale, strength)
143
- if (n === 0) { console.warn(`Lightmap: ${e.key} was not loaded with { lightmap: true } — skipped`); continue }
144
- _creator.setGlbShadows?.(e.model.id, false, true)
145
- } else if (e.mesh) {
146
- Lightmap.swapMeshMaterial(e.mesh, texture, st, ambientScale, strength)
147
- e.mesh.castShadows = false
148
- }
149
- applied++
150
- }
151
- 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`)
152
- return { size: data.size, texel: data.texel, applied, total: rects.size, missing }
153
- }
154
-
155
- /** A Mesh keeps its look (colour / map / roughness / metallic) but moves to the lightmap material. */
156
- private static swapMeshMaterial(mesh: Mesh, texture: Texture, st: number[], ambientScale: number, strength: number): void {
157
- const src = mesh.material
158
- const u = src?.uniforms ?? {}
159
- const m = Material.lightmap()
160
- m.set("baseColorFactor", hex8(u.baseColor))
161
- if (u.baseColorMap instanceof Texture) m.set("baseColorMap", u.baseColorMap)
162
- if (typeof u.roughness === "number") m.set("roughnessFactor", u.roughness)
163
- if (typeof u.metallic === "number") m.set("metallicFactor", u.metallic)
164
- m.set("lightmap", texture)
165
- m.set("lightmapST", [ st[0], st[1], st[2], st[3] ])
166
- m.set("ambientScale", ambientScale)
167
- m.set("sunStrength", strength)
168
- mesh.setMaterial(m)
169
- }
170
-
171
- private static bake(cfg: BakeConfig): void {
172
- 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 }
173
- const ids = Uint32Array.from(Lightmap.entries.map((e) => (e.model ?? e.mesh)!.id))
174
- const keys = Lightmap.entries.map((e) => e.key).join("\n")
175
- if (ids.length === 0) { console.error("[lightmap] error: nothing registered — call Lightmap.add on the static props before Lightmap.load"); return }
176
- _creator.lightmapBake(cfg.outDir, ids, keys, cfg.size ?? 4096, cfg.texel ?? 0.02, cfg.sunRays ?? 32, cfg.aoRays ?? 64,
177
- cfg.aoDistance ?? 2, cfg.bias ?? 0.02, cfg.sunAngle ?? 0.5)
178
- }
179
- }
@@ -1,245 +0,0 @@
1
- // A material instance over creator-gl. Uniforms are set through a Proxy that lowers each JS value to
2
- // the right native uniform call. The common materials are static factories (Material.lit/unlit/
3
- // video/shadow); Material.load brings in a custom compiled shader. Ported from worker/src/Material.ts,
4
- // with the Lit/Unlit/... subclasses collapsed into factories and color accepting any ColorInput.
5
-
6
- import { Color, type ColorInput } from "../core/color"
7
- import { fetch, type FetchResponse } from "../runtime/fetch"
8
- import { Texture } from "./Texture"
9
- // Type-only (erased at build) so a 3D material never pulls the 2D-canvas runtime; the guard is inline.
10
- import type { Canvas } from "../canvas/Canvas"
11
-
12
- /** Duck-type a Canvas without importing it (keeps 3D material free of 2D-canvas code). */
13
- const isCanvasSource = (v: unknown): v is Canvas =>
14
- typeof v === "object" && v !== null && (v as { __canvas?: unknown }).__canvas === true
15
-
16
- type UniformValue = string | Texture | number | boolean | number[] | Float32Array | null
17
-
18
- const applyUniform = (id: number, key: string, value: UniformValue): void => {
19
- if (typeof value === "string") {
20
- if (value.length > 7) _creator.setUniformRgba(id, key, parseInt(value.slice(1), 16))
21
- else _creator.setUniformRgb(id, key, parseInt(value.slice(1), 16))
22
- } else if (typeof value === "number") {
23
- _creator.setUniformFloat(id, key, value)
24
- } else if (typeof value === "boolean") {
25
- _creator.setUniformBoolean(id, key, value)
26
- } else if (value instanceof Texture) {
27
- _creator.setUniformTexture(id, key, value._id, value.wrapS, value.wrapT)
28
- } else if (value instanceof Float32Array) {
29
- _creator.setUniformArray(id, key, value)
30
- } else if (Array.isArray(value)) {
31
- _creator.setUniformArray(id, key, Float32Array.of(...value))
32
- } else {
33
- _creator.setUniformNull(id, key)
34
- }
35
- }
36
-
37
- export type MaterialColorOptions = { color?: ColorInput, map?: Texture | Canvas | null }
38
-
39
- /** `Material.particles` options — the default point-sprite material for particle systems. */
40
- export type ParticlesMaterialOptions = {
41
- /** Sprite texture — a single image or a flipbook sheet of frames. Unset = soft round dot. */
42
- map?: Texture | null
43
- /** Flipbook grid: `N` = an N×N sheet, `[cols, rows]` = a non-square one. Frames run
44
- * left-to-right, top-to-bottom; drive the index via `particles.frame`. Default 1. */
45
- sheet?: number | [number, number]
46
- /** Extra brightness multiplier for a glow look (0 = off). */
47
- emissive?: number
48
- /** (`render` quad / stretch) a second sheet on the same grid, ADDED as `emissiveMap × emissiveColor`
49
- * — keyed by `map`'s alpha, not tinted by the particle colour. A fireball = a smoke `map` whose
50
- * colour curve goes dark + a fire `emissiveMap` that keeps glowing while alpha lasts. */
51
- emissiveMap?: Texture | null
52
- /** Multiplier for `emissiveMap`; an `[r, g, b]` array may exceed 1 (HDR glow). Default 0 (off). */
53
- emissiveColor?: ColorInput
54
- /** (`render` quad / stretch) crossfade each flipbook frame into the next by the fractional frame
55
- * index instead of snapping — smooth sheet playback at any frame rate. Default false. */
56
- frameBlend?: boolean
57
- /** Compositing: `"alpha"` (default — smoke, dust) or `"add"` (fire, sparks, magic). */
58
- blend?: "alpha" | "add"
59
- /** Soft radial falloff toward the sprite edge. Defaults to true without a map, false with one. */
60
- soft?: boolean
61
- /** Soft particles: fade each sprite out over this many metres as it cuts into the scene geometry
62
- * behind it (Unity's "Soft Particles Factor"), so smoke and dust no longer slice through the
63
- * ground and walls with a hard edge. 0 = off (default). Reads the scene's depth buffer, which the
64
- * engine keeps bound while any soft system is on screen — the extra depth pass is only paid then.
65
- * Filament hosts (desktop / web / Android / Apple); web-lite draws such sprites unfaded. */
66
- depthFade?: number
67
- /** How each particle is drawn: `'point'` (default — GPU point sprites, cheapest), `'quad'`
68
- * (real camera-facing quads — no driver size cap, geometric rotation), or `'stretch'`
69
- * (quads stretched along velocity — sparks, rain, speed streaks). `'ribbon'` is what `Trail`
70
- * uses internally. */
71
- render?: "point" | "quad" | "stretch" | "ribbon"
72
- /** (`render: 'stretch'`) extra sprite length in world units per unit of speed:
73
- * length = size + stretch·|velocity|. Default 0.05. */
74
- stretch?: number
75
- }
76
-
77
- /** @internal — the sheet option as whole [cols, rows], both ≥ 1. The particles shader divides
78
- * by these with no guard of its own, so this clamp is the only thing between it and a NaN UV. */
79
- export const _sheetColsRows = (sheet: number | [number, number] | undefined): [number, number] => {
80
- const [ c, r ] = typeof sheet === "number" ? [ sheet, sheet ] : sheet ?? [ 1, 1 ]
81
- return [ Math.max(1, Math.floor(c) || 1), Math.max(1, Math.floor(r) || 1) ]
82
- }
83
-
84
- /** `Material.lit` options — PBR scalars on top of the color/map pair. */
85
- export type LitMaterialOptions = MaterialColorOptions & {
86
- /** Perceptual roughness, 0 (mirror) … 1 (matte). Unset = the shader's default. */
87
- roughness?: number
88
- /** Metallic factor, 0 (dielectric) … 1 (metal). Unset = the shader's default. */
89
- metallic?: number
90
- }
91
-
92
- export class Material {
93
- /** @internal native material-instance handle. */
94
- _id: number
95
- readonly shader: FetchResponse | "unknown"
96
- readonly uniforms: Record<string, UniformValue>
97
-
98
- private _colorKey: string | null = null
99
- private _useMapFlag = false
100
-
101
- constructor(shader: FetchResponse | number) {
102
- if (typeof shader === "number") {
103
- this.shader = "unknown"
104
- this._id = shader
105
- } else {
106
- this.shader = shader
107
- this._id = _creator.createMaterial((shader as any)._id)
108
- }
109
- const store: Record<string, UniformValue> = {}
110
- const id = () => this._id
111
- this.uniforms = new Proxy(store, {
112
- get: (t, k: string) => t[k],
113
- set: (t, k: string, v: UniformValue) => { t[k] = v; applyUniform(id(), k, v); return true },
114
- })
115
- }
116
-
117
- /** Set a uniform (chainable). */
118
- set(key: string, value: UniformValue): this {
119
- this.uniforms[key] = value
120
- return this
121
- }
122
-
123
- set color(c: ColorInput) {
124
- if (this._colorKey) this.uniforms[this._colorKey] = Color.toHexString(c)
125
- }
126
-
127
- // lit/unlit shade baseColor × baseColorMap (factor×map; engines default both to white), so
128
- // removing the map means resetting baseColorMap to the white 1×1. The useBaseColorMap writes
129
- // stay for older hosts whose shaders still branch on the flag — current engines ignore it.
130
- set map(value: Texture | Canvas | null) {
131
- if (value === null) {
132
- this.uniforms.baseColorMap = null
133
- if (this._useMapFlag) this.uniforms.useBaseColorMap = false
134
- } else {
135
- // A Canvas bakes to a (cached) 3D texture on assign — same as Sprite accepting a Canvas in 2D.
136
- this.uniforms.baseColorMap = isCanvasSource(value) ? Texture.fromCanvas(value) : value
137
- if (this._useMapFlag) this.uniforms.useBaseColorMap = true
138
- }
139
- }
140
-
141
- // --- factories ---
142
-
143
- /** PBR lit material. */
144
- static lit(options: LitMaterialOptions = {}): Material {
145
- // The fetchLocal literal must be inline so the compiler's preload header captures the shader name.
146
- const m = new Material({ _id: _creatorUtils.fetchLocal("lit.filamat") } as any)
147
- m._colorKey = "baseColor"
148
- if (options.color !== undefined) m.color = options.color
149
- if (options.map) m.map = options.map
150
- if (options.roughness !== undefined) m.uniforms.roughness = options.roughness
151
- if (options.metallic !== undefined) m.uniforms.metallic = options.metallic
152
- return m
153
- }
154
-
155
- /** Unlit (flat) material. */
156
- static unlit(options: MaterialColorOptions = {}): Material {
157
- const m = new Material({ _id: _creatorUtils.fetchLocal("unlit.filamat") } as any)
158
- m._colorKey = "baseColor"
159
- if (options.color !== undefined) m.color = options.color
160
- if (options.map) m.map = options.map
161
- return m
162
- }
163
-
164
- /** Default particle material — camera-facing point sprites with tint/opacity/rotation/flipbook
165
- * driven by the particle curves. Uniforms all default to 0 on a fresh instance, so every look
166
- * knob is primed here; JS writes after construction override them. */
167
- static particles(options: ParticlesMaterialOptions = {}): Material {
168
- const quad = options.render !== undefined && options.render !== "point"
169
- // Both fetchLocal literals inline so the compiler's preload header captures the shader names.
170
- const m = quad
171
- ? new Material({ _id: _creatorUtils.fetchLocal("particles-quad.filamat") } as any)
172
- : new Material({ _id: _creatorUtils.fetchLocal("particles.filamat") } as any)
173
- m._useMapFlag = false
174
- m.uniforms.sheetSize = _sheetColsRows(options.sheet)
175
- m.uniforms.emissive = options.emissive ?? 0
176
- m.uniforms.additive = options.blend === "add" ? 1 : 0
177
- m.uniforms.softness = (options.soft ?? !options.map) ? 1 : 0
178
- m.uniforms.depthFade = options.depthFade ?? 0
179
- if (quad) {
180
- m.uniforms.mode = options.render === "ribbon" ? 2 : options.render === "stretch" ? 1 : 0
181
- m.uniforms.stretch = options.stretch ?? (options.render === "stretch" ? 0.05 : 0)
182
- if (options.emissiveMap) m.uniforms.emissiveMap = options.emissiveMap
183
- if (options.emissiveColor !== undefined) m.uniforms.emissiveColor = Color.toRgb01(options.emissiveColor)
184
- if (options.frameBlend) m.uniforms.frameBlend = 1
185
- }
186
- if (options.map) m.map = options.map
187
- return m
188
- }
189
-
190
- /** Material that samples a VideoPlayer's texture. */
191
- static video(map?: Texture): Material {
192
- const m = new Material({ _id: _creatorUtils.fetchLocal("video.filamat") } as any)
193
- m._useMapFlag = false
194
- if (map) m.uniforms.baseColorMap = map
195
- return m
196
- }
197
-
198
- /** The lightmap material (docs/lightmap-plan.md): PBR base colour × a baked shadow/AO atlas on UV1.
199
- * Models take it through `Model.load(…, { lightmap: true })`; `Lightmap.load` builds one per static
200
- * Mesh. Parameters use gltfio's names (`baseColorFactor`, `baseColorMap`, `roughnessFactor`,
201
- * `metallicFactor`) plus `lightmap`, `lightmapST`, `ambientScale`, `sunStrength` (the sun / IBL terms come from
202
- * filament's per-frame uniforms inside the shader). */
203
- static lightmap(): Material {
204
- // Inline literal: the compiler's preload header captures the shader name from it.
205
- const m = new Material({ _id: _creatorUtils.fetchLocal("lightmap.filamat") } as any)
206
- m.set("baseColorFactor", "#ffffffff").set("roughnessFactor", 1).set("metallicFactor", 0)
207
- m.set("lightmapST", [ 1, 1, 0, 0 ]).set("ambientScale", 1).set("sunStrength", 1).set("hasNormalMap", 0)
208
- return m
209
- }
210
-
211
- /** @internal the shared template instance `Model.load({ lightmap })` hands the engine (its MATERIAL is what
212
- * the GLB provider instantiates; the template itself never draws). */
213
- static _lightmapTemplate(): Material { return (Material._lmTemplate ??= Material.lightmap()) }
214
- private static _lmTemplate: Material | undefined
215
-
216
- /** Shadow-catcher material (transparent except where shadows fall). */
217
- static shadow(color: ColorInput = "#000000aa"): Material {
218
- const m = new Material({ _id: _creatorUtils.fetchLocal("shadow.filamat") } as any)
219
- m._colorKey = "color"
220
- m.color = color
221
- return m
222
- }
223
-
224
- /** Load a custom compiled shader (.mat URL) as a material. */
225
- static load(url: string): Promise<Material> {
226
- // "id:<n>" — the artifact is a LOCAL file the host already resolved through fetchLocal (a
227
- // compile with no asset server: the desktop host reads it from next to the bundle). There is
228
- // nothing to fetch, so the material is ready synchronously; -1 means the host found no such
229
- // file, which would otherwise reach createMaterial as a bad handle.
230
- if (url.startsWith("id:")) {
231
- const id = Number(url.slice(3))
232
- if (!(id >= 0)) {
233
- return Promise.reject(new Error(`Shader not found next to the app (${url}). Compile it with \`lecodes shaders compile\`.`))
234
- }
235
- return Promise.resolve(new Material({ _id: id } as any))
236
- }
237
- return fetch(url, { useOnce: true }).then((resp) => {
238
- if (resp.status >= 400) return Promise.reject(new Error(`Failed to fetch shader from ${url}. HTTP ${resp.status}`))
239
- return new Material(resp)
240
- })
241
- }
242
-
243
- /** @internal */
244
- static idOf(material: Material): number { return material._id }
245
- }