lecodes-cli 0.17.2 → 0.18.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 (62) hide show
  1. package/README.md +1 -1
  2. package/dist/index.js +2376 -755
  3. package/package.json +4 -4
  4. package/runtime/scene-harness.json +1 -1
  5. package/runtime/sdk/compile/aspectMacro.ts +52 -8
  6. package/runtime/sdk/compile/assetMacro.ts +116 -15
  7. package/runtime/sdk/compile/bundler.ts +39 -4
  8. package/runtime/sdk/compile/compileProject.ts +16 -1
  9. package/runtime/sdk/compile/header.ts +6 -1
  10. package/runtime/sdk/compile/index.ts +31 -0
  11. package/runtime/sdk/compile/liteMaterial.ts +247 -0
  12. package/runtime/sdk/compile/sceneEditor.ts +11 -1
  13. package/runtime/sdk/compile/shaderSchema.ts +202 -0
  14. package/runtime/sdk/compile/shaderTargets.ts +81 -0
  15. package/runtime/sdk/core/Aspect.ts +363 -95
  16. package/runtime/sdk/core/compWrite.ts +42 -0
  17. package/runtime/sdk/core/fields.ts +1 -1
  18. package/runtime/sdk/core/time.ts +81 -0
  19. package/runtime/sdk/g2/Camera2D.ts +8 -1
  20. package/runtime/sdk/g2/CharacterController2D.ts +253 -53
  21. package/runtime/sdk/g2/Node2D.ts +80 -10
  22. package/runtime/sdk/g2/OneWay2D.ts +66 -0
  23. package/runtime/sdk/g2/Physics2D.ts +240 -30
  24. package/runtime/sdk/g2/Scene2D.ts +33 -1
  25. package/runtime/sdk/g2/Shape2D.ts +218 -22
  26. package/runtime/sdk/g2/Trigger2D.ts +42 -12
  27. package/runtime/sdk/g2/groups2d.ts +106 -0
  28. package/runtime/sdk/g2/loop.ts +15 -4
  29. package/runtime/sdk/gl/Camera.ts +41 -0
  30. package/runtime/sdk/gl/CameraPlace.ts +52 -0
  31. package/runtime/sdk/gl/CharacterController.ts +184 -55
  32. package/runtime/sdk/gl/Gearbox.ts +212 -0
  33. package/runtime/sdk/gl/Geometry.ts +70 -9
  34. package/runtime/sdk/gl/IK.ts +193 -174
  35. package/runtime/sdk/gl/Light.ts +64 -2
  36. package/runtime/sdk/gl/Lightmap.ts +179 -0
  37. package/runtime/sdk/gl/Material.ts +36 -0
  38. package/runtime/sdk/gl/Mesh.ts +6 -23
  39. package/runtime/sdk/gl/Model.ts +23 -8
  40. package/runtime/sdk/gl/Node.ts +350 -285
  41. package/runtime/sdk/gl/Physics.ts +222 -126
  42. package/runtime/sdk/gl/Scene.ts +175 -8
  43. package/runtime/sdk/gl/Shape.ts +255 -12
  44. package/runtime/sdk/gl/Trigger.ts +1 -6
  45. package/runtime/sdk/gl/Vehicle.ts +473 -0
  46. package/runtime/sdk/gl/Wheel.ts +240 -0
  47. package/runtime/sdk/gl/{AnimationClip.ts → animation/AnimationClip.ts} +37 -7
  48. package/runtime/sdk/gl/animation/Animator.ts +87 -0
  49. package/runtime/sdk/gl/animation/Layer.ts +29 -0
  50. package/runtime/sdk/gl/animation/Loop.ts +25 -0
  51. package/runtime/sdk/gl/animation/Playback.ts +43 -0
  52. package/runtime/sdk/gl/animation/core.ts +294 -0
  53. package/runtime/sdk/gl/scenarios.ts +291 -349
  54. package/runtime/sdk/inject.ts +186 -162
  55. package/runtime/sdk/runtime/app.ts +13 -0
  56. package/runtime/sdk/runtime/input.ts +169 -6
  57. package/runtime/sdk/scene/defineScene.ts +1227 -1016
  58. package/runtime/sdk/scene/gizmos.ts +148 -0
  59. package/runtime/sdk/scene/material.ts +188 -0
  60. package/runtime/sdk-types.json +1 -1
  61. package/runtime/sdk/gl/Animator.ts +0 -642
  62. package/runtime/sdk/gl/ModelAnimation.ts +0 -95
@@ -58,6 +58,12 @@ export type ParticlesMaterialOptions = {
58
58
  blend?: "alpha" | "add"
59
59
  /** Soft radial falloff toward the sprite edge. Defaults to true without a map, false with one. */
60
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
61
67
  /** How each particle is drawn: `'point'` (default — GPU point sprites, cheapest), `'quad'`
62
68
  * (real camera-facing quads — no driver size cap, geometric rotation), or `'stretch'`
63
69
  * (quads stretched along velocity — sparks, rain, speed streaks). `'ribbon'` is what `Trail`
@@ -169,6 +175,7 @@ export class Material {
169
175
  m.uniforms.emissive = options.emissive ?? 0
170
176
  m.uniforms.additive = options.blend === "add" ? 1 : 0
171
177
  m.uniforms.softness = (options.soft ?? !options.map) ? 1 : 0
178
+ m.uniforms.depthFade = options.depthFade ?? 0
172
179
  if (quad) {
173
180
  m.uniforms.mode = options.render === "ribbon" ? 2 : options.render === "stretch" ? 1 : 0
174
181
  m.uniforms.stretch = options.stretch ?? (options.render === "stretch" ? 0.05 : 0)
@@ -188,6 +195,24 @@ export class Material {
188
195
  return m
189
196
  }
190
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
+
191
216
  /** Shadow-catcher material (transparent except where shadows fall). */
192
217
  static shadow(color: ColorInput = "#000000aa"): Material {
193
218
  const m = new Material({ _id: _creatorUtils.fetchLocal("shadow.filamat") } as any)
@@ -198,6 +223,17 @@ export class Material {
198
223
 
199
224
  /** Load a custom compiled shader (.mat URL) as a material. */
200
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
+ }
201
237
  return fetch(url, { useOnce: true }).then((resp) => {
202
238
  if (resp.status >= 400) return Promise.reject(new Error(`Failed to fetch shader from ${url}. HTTP ${resp.status}`))
203
239
  return new Material(resp)
@@ -24,7 +24,6 @@ export type MeshOptions = {
24
24
 
25
25
  export class Mesh extends Node {
26
26
  private _geometry?: Geometry
27
- private _materials: Material[] = []
28
27
 
29
28
  constructor(geometry?: Geometry, material?: Material) {
30
29
  super()
@@ -32,29 +31,15 @@ export class Mesh extends Node {
32
31
  const mat = material ?? Material.unlit()
33
32
  this._geometry = geometry
34
33
  this._materials = [ mat ]
35
- _creator.setMesh(this.id, Material.idOf(mat), geometry.vertices, geometry.normals, geometry.indices, geometry.uv, geometry._meshKind)
34
+ _creator.setMesh(this.id, Material.idOf(mat), geometry.vertices, geometry.normals, geometry.indices, geometry.uv, geometry._meshKind, geometry.uv1)
36
35
  }
37
36
  }
38
37
 
39
38
  override get geometry(): Geometry | undefined { return this._geometry }
40
39
 
41
- get material(): Material { return this._materials[0] }
42
- set material(m: Material) { this.setMaterial(m, 0) }
43
-
44
- setMaterial(material: Material, index = 0): this {
45
- this._materials[index] = material
46
- _creator.setMaterial(this.id, Material.idOf(material), index)
47
- return this
48
- }
49
- getMaterial(index = 0): Material | null {
50
- const existing = this._materials[index]
51
- if (existing) return existing
52
- const id = _creator.getMaterial(this.id, index)
53
- if (id === 0) return null
54
- const mat = new Material(id)
55
- this._materials[index] = mat
56
- return mat
57
- }
40
+ /** A Mesh always carries a material (slot 0) — see Node.setMaterial for the slot API. */
41
+ override get material(): Material { return this._materials![0]! }
42
+ override set material(m: Material) { this.setMaterial(m, 0) }
58
43
 
59
44
  set culling(v: boolean) { _creator.setCulling(this.id, v) }
60
45
  set castShadows(v: boolean) { _creator.setCastShadows(this.id, v) }
@@ -63,10 +48,8 @@ export class Mesh extends Node {
63
48
  // --- factories ---
64
49
 
65
50
  static box(options: MeshOptions & { size?: Vec3Like | number } = {}): Mesh {
66
- const geo = Geometry.box()
67
- const [ sx, sy, sz ] = sizeToVec(options.size ?? 1)
68
- if (sx !== 1 || sy !== 1 || sz !== 1) geo.scale(sx, sy, sz)
69
- return apply(new Mesh(geo, options.material), options)
51
+ // the size goes into the geometry (not a scale after): the lightmap chart is laid out per face area
52
+ return apply(new Mesh(Geometry.box(options.size ?? 1), options.material), options)
70
53
  }
71
54
 
72
55
  static sphere(options: MeshOptions & SphereOptions = {}): Mesh {
@@ -1,20 +1,24 @@
1
1
  // A loaded GLB model — its own node kind (a GLB is a node hierarchy with baked animation clips),
2
2
  // distinct from Mesh (raw primitives, no animation). Animation is always present, reached as
3
- // model.anim:
3
+ // model.anim (an Animator over the GLB's clips — crossfades, blend spaces, layers when you need them):
4
4
  // const hero = await Model.load(asset('./hero.glb'))
5
- // hero.anim.play('Run')
5
+ // hero.anim.play('Run', { loop: true })
6
6
 
7
7
  import { fetch, type FetchResponse } from "../runtime/fetch"
8
8
  import { Node } from "./Node"
9
- import { ModelAnimation } from "./ModelAnimation"
9
+ import { Animator } from "./animation/Animator"
10
+ import { Material } from "./Material"
10
11
 
11
12
  export class Model extends Node {
12
- /** GLB skeletal animation — always present on a Model. */
13
- declare readonly anim: ModelAnimation
13
+ /** The model's Animator — always present, its clip table = the GLB's embedded clips. Configure
14
+ * more (external clips, blend spaces, layers) with `model.aspect(Animator, {...})`. */
15
+ declare readonly anim: Animator
16
+ /** @internal loaded through the lightmap material (docs/lightmap-plan.md); clones inherit it. */
17
+ _lightmapped = false
14
18
 
15
19
  constructor(internalId: number) {
16
20
  super(internalId)
17
- this.aspect(ModelAnimation)
21
+ this.aspect(Animator)
18
22
  }
19
23
 
20
24
  /** Duplicate this model — a deep copy of the GLB (meshes, skeleton, animation clips), attached to
@@ -23,13 +27,21 @@ export class Model extends Node {
23
27
  clone(): Model {
24
28
  const id = _creator.cloneEntity(this.id)
25
29
  _creator.setGlbCulling(id, false)
26
- return new Model(id)
30
+ const m = new Model(id)
31
+ m._lightmapped = this._lightmapped
32
+ return m
27
33
  }
28
34
 
29
35
  /** Load a GLB model. Returns its root as a Model; play its baked clips via model.anim. */
30
36
  static load(
31
37
  source: string | FetchResponse,
32
- options: { culling?: boolean, onProgress?: (p: { loaded: number, total?: number }) => void } = {},
38
+ options: {
39
+ culling?: boolean
40
+ onProgress?: (p: { loaded: number, total?: number }) => void
41
+ /** Load through the lightmap material so `Lightmap.load` can bind a baked atlas (the GLB needs
42
+ * TEXCOORD_1 — `lecodes assets doctor --lightmap-uv`). Hosts without lightmap support ignore it. */
43
+ lightmap?: boolean
44
+ } = {},
33
45
  ): Promise<Model> {
34
46
  if (typeof source === "string") {
35
47
  return fetch(source, { useOnce: true, onProgress: options.onProgress }).then((resp) => {
@@ -38,9 +50,12 @@ export class Model extends Node {
38
50
  })
39
51
  }
40
52
  return new Promise<Model>((resolve, reject) => {
53
+ const lightmapped = !!options.lightmap && !!_creator.setNextGlbLightmapped
54
+ if (lightmapped) _creator.setNextGlbLightmapped!(Material.idOf(Material._lightmapTemplate()))
41
55
  _creator.createGlb((source as unknown as { _id: number })._id, (entityId: number) => {
42
56
  if (entityId === 0) { reject(new Error("Failed to load GLB")); return }
43
57
  const model = new Model(entityId)
58
+ model._lightmapped = lightmapped
44
59
  if (options.culling !== true) _creator.setGlbCulling(entityId, false)
45
60
  resolve(model)
46
61
  }, reject)