lecodes-sdk 2.4.5 → 2.4.7
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.
- package/dist/types/gl/Lightmap.d.ts +25 -4
- package/dist/types/gl/Model.d.ts +4 -4
- package/dist/types/runtime/app.d.ts +14 -0
- package/dist/types/scene/defineScene.d.ts +15 -2
- package/dist/types/version.d.ts +1 -1
- package/dist/types.json +1 -1
- package/package.json +1 -1
- package/src/bridges/app.d.ts +8 -1
- package/src/bridges/gl.d.ts +38 -17
- package/src/gl/Lightmap.ts +127 -48
- package/src/gl/Model.ts +4 -4
- package/src/math/vec.ts +1 -1
- package/src/runtime/app.ts +14 -0
- package/src/scene/defineScene.ts +28 -13
- package/src/version.ts +1 -1
package/src/scene/defineScene.ts
CHANGED
|
@@ -188,8 +188,12 @@ export type SceneNodeDef = {
|
|
|
188
188
|
/** Baked lighting (a scene with `env.lightmap`): is this model/mesh node a lightmap STATIC — a
|
|
189
189
|
* receiver and an occluder in the bake, real-time shadow casting off once the bake applies?
|
|
190
190
|
* Default: static unless a `Physics` aspect moves the node (`dynamic` — Physics' default — or
|
|
191
|
-
* `kinematic`). Set it only to override that rule; prefab subtrees inherit the verdict.
|
|
192
|
-
|
|
191
|
+
* `kinematic`). Set it only to override that rule; prefab subtrees inherit the verdict.
|
|
192
|
+
* `'cube'` = a static whose every mesh bakes as AMBIENT CUBES instead of atlas texels — an occluder (its shadow and
|
|
193
|
+
* bounce stay in the bake) lit where it stands, the way the bake treats small and thin props on its own: a pipe run, a
|
|
194
|
+
* fence, a prop whose lightmap seams show. A prefab marked `'cube'` bakes its statics that way too. By name for a whole
|
|
195
|
+
* kit: `env.lightmap.cube`. */
|
|
196
|
+
lightmap?: boolean | "cube"
|
|
193
197
|
/** `model` nodes: vegetation — load through the FOLIAGE tier (wind, touch bending, per-copy tint; see
|
|
194
198
|
* `Foliage`). `{ fade: [start, end] }` also thins the cards out over that distance range (m) and drops the
|
|
195
199
|
* copy past `end` — for ground cover. The fade is per ASSET: every node of the same GLB shares it. */
|
|
@@ -243,10 +247,19 @@ export type SceneLightmapDef = {
|
|
|
243
247
|
* `{ through, diffuse }` splits it: `through` goes straight (the picture of the map), `diffuse` is scattered by the
|
|
244
248
|
* fibres (the underside glows, a soft fill with no picture) - dense canvas: `{ through: 0.08, diffuse: 0.25 }`. */
|
|
245
249
|
transmit?: Record<string, LightmapTransmit>
|
|
250
|
+
/** FOR THE BAKE: CUBES BY NAME — regular expressions (ECMAScript, case-sensitive) searched in every baked mesh's key
|
|
251
|
+
* (`<node path>@<ordinal>`): `['SM_pipe', 'SM_fence']` bakes every pipe and fence node as ambient cubes instead of atlas
|
|
252
|
+
* texels, like a node's `lightmap: 'cube'`. The CLI's `--cube` adds to these. */
|
|
253
|
+
cube?: string[]
|
|
246
254
|
/** `asset('./assets/lightmap/level.lgrid')` — THE LIGHT GRID for movers: every node a Physics aspect or a
|
|
247
255
|
* CharacterController moves (and what code marks with `Lightmap.track`) takes its ambient light from the bake at the
|
|
248
256
|
* place it is at, whatever its shader. Absent = movers keep the scene's IBL. */
|
|
249
257
|
volume?: string
|
|
258
|
+
/** `asset('./assets/lightmap/level-uv1.bin')` — THE PACKED LIGHTMAP UV1 the bake's rects map: it packs every mesh's
|
|
259
|
+
* lightmap charts into a square of their own instead of the unwrap's (once per mesh, whatever model holds it), and the
|
|
260
|
+
* engine swaps this into the models' TEXCOORD_1 before the rects go on. Required when the bake wrote one (the .bake's `uv1`): without it the statics
|
|
261
|
+
* read the wrong texels, so the bake is not applied. */
|
|
262
|
+
uv1?: string
|
|
250
263
|
/** A DEBUG multiplier on the atlas' lux (default 1). */
|
|
251
264
|
lightScale?: number
|
|
252
265
|
}
|
|
@@ -430,12 +443,13 @@ const createMesh = async (def: MeshDef, material?: MaterialDef): Promise<Mesh> =
|
|
|
430
443
|
|
|
431
444
|
// ---- baked lighting (env.lightmap) ---------------------------------------------------------------
|
|
432
445
|
// Statics are derived, not declared: a model/mesh node bakes unless a Physics aspect moves it
|
|
433
|
-
// (`dynamic` — Physics' default — or `kinematic`); `lightmap: true | false` on the node overrides
|
|
434
|
-
//
|
|
435
|
-
//
|
|
446
|
+
// (`dynamic` — Physics' default — or `kinematic`); `lightmap: true | false | 'cube'` on the node overrides ('cube': a static
|
|
447
|
+
// baked as ambient cubes, its prefab internals too — LightmapCtx.cube).
|
|
448
|
+
// Keys are node paths (prefab internals: `wrapper/inner`; a model's meshes are records `<path>@<ordinal>` of their own), so
|
|
449
|
+
// lightmap.bake stays readable and a rename honestly invalidates the rect. make() subtrees are code — they register themselves with
|
|
436
450
|
// Lightmap.add. Edit mode never bakes or applies (the editor shows real-time shadows).
|
|
437
451
|
|
|
438
|
-
type LightmapCtx = { prefix: string }
|
|
452
|
+
type LightmapCtx = { prefix: string, cube?: boolean }
|
|
439
453
|
|
|
440
454
|
// A CharacterController moves its node as surely as a dynamic body does (2026-09-11: the hero's
|
|
441
455
|
// capsule was read as static, so its sword baked a shadow into the terrain at the spawn point).
|
|
@@ -446,7 +460,7 @@ const movesByPhysics = (def: SceneNodeDef): boolean =>
|
|
|
446
460
|
|
|
447
461
|
/** The static verdict for one node def (the editor's "Baked lighting" switch shows the same rule).
|
|
448
462
|
* Children of a mover are movers too — buildNodes carries that down, this is the node's own verdict. */
|
|
449
|
-
export const isLightmapStatic = (def: SceneNodeDef): boolean => def.lightmap
|
|
463
|
+
export const isLightmapStatic = (def: SceneNodeDef): boolean => def.lightmap !== undefined ? def.lightmap !== false : !movesByPhysics(def)
|
|
450
464
|
|
|
451
465
|
/** A terrain node: from its file or a fresh flat grid; textures load before the node appears (like a
|
|
452
466
|
* mesh's material). Stamps and holes apply later in buildNodes, once every target node exists. */
|
|
@@ -905,7 +919,8 @@ const buildNodes = async (
|
|
|
905
919
|
// the path is derived from def keys BEFORE any await, so it is deterministic even though
|
|
906
920
|
// Promise.all makes build completion (and record insertion) order nondeterministic
|
|
907
921
|
const path = parentPath === "" ? name : `${parentPath}/${name}`
|
|
908
|
-
const lmStatic = lm !== null && (nd.lightmap
|
|
922
|
+
const lmStatic = lm !== null && (nd.lightmap !== undefined ? nd.lightmap !== false : !parentMoves && isLightmapStatic(nd))
|
|
923
|
+
const lmCube = lmStatic && (nd.lightmap === "cube" || lm!.cube === true)
|
|
909
924
|
// a mover in a level with a light volume reads the volume (models: the engine binds it; meshes: registered below)
|
|
910
925
|
const node = await createSource(path, nd, lmStatic)
|
|
911
926
|
// a `mount` def parents to an internal part of the parent asset — safe here: the parent's
|
|
@@ -914,13 +929,13 @@ const buildNodes = async (
|
|
|
914
929
|
// Draw-set membership is separate from parenting (see docs/3d/node.md) — every def node joins.
|
|
915
930
|
scene.add(node)
|
|
916
931
|
applyNode(node, name, nd) // engine-side name stays the bare sibling segment
|
|
917
|
-
if (lmStatic && (node instanceof Model || node instanceof Mesh)) Lightmap._register(node, lm!.prefix + path)
|
|
918
|
-
else if (lmStatic && nd.terrain !== undefined) Lightmap._registerGround(node, lm!.prefix + path)
|
|
932
|
+
if (lmStatic && (node instanceof Model || node instanceof Mesh)) Lightmap._register(node, lm!.prefix + path, lmCube)
|
|
933
|
+
else if (lmStatic && nd.terrain !== undefined) Lightmap._registerGround(node, lm!.prefix + path, lmCube)
|
|
919
934
|
// the top of a moving subtree: everything under it reads the level's light grid (Lightmap.track walks it at load)
|
|
920
935
|
if (lm !== null && !lmStatic && !parentMoves) moverRoots.push(node)
|
|
921
936
|
if (EDITOR && isEditMode()) addEditorMarker(node, nd)
|
|
922
937
|
// a prefab under a static wrapper bakes; under a mover its internals stay movers (volume-lit)
|
|
923
|
-
if (nd.prefab !== undefined) await attachPrefab(node, path, nd, scene, stack, lmStatic ? { prefix: `${lm!.prefix}${path}
|
|
938
|
+
if (nd.prefab !== undefined) await attachPrefab(node, path, nd, scene, stack, lmStatic ? { prefix: `${lm!.prefix}${path}/`, cube: lmCube } : null)
|
|
924
939
|
pending.push({ path, node, def: nd })
|
|
925
940
|
nodes[path] = node
|
|
926
941
|
await Promise.all(Object.entries(nd.children ?? {}).map(([ childName, child ]) => build(childName, child, node, path, parentMoves || (lm !== null && !lmStatic))))
|
|
@@ -1020,8 +1035,8 @@ const instantiate = async (def: SceneDef, editorRuns: EditorRun[]): Promise<{ sc
|
|
|
1020
1035
|
|
|
1021
1036
|
// the level is complete: apply the bake — or, under `lecodes lightmap bake`, run it
|
|
1022
1037
|
if (lm && lightmap) {
|
|
1023
|
-
await Lightmap.load(scene, { data: lightmap.data, light: lightmap.light, aux: lightmap.aux, direct: lightmap.direct, indirect: lightmap.indirect, probes: lightmap.probes, volume: lightmap.volume },
|
|
1024
|
-
{ lightScale: lightmap.lightScale, emissiveNits: lightmap.emissiveNits, emissiveBake: lightmap.emissiveBake, transmit: lightmap.transmit })
|
|
1038
|
+
await Lightmap.load(scene, { data: lightmap.data, light: lightmap.light, aux: lightmap.aux, direct: lightmap.direct, indirect: lightmap.indirect, probes: lightmap.probes, volume: lightmap.volume, uv1: lightmap.uv1 },
|
|
1039
|
+
{ lightScale: lightmap.lightScale, emissiveNits: lightmap.emissiveNits, emissiveBake: lightmap.emissiveBake, transmit: lightmap.transmit, cube: lightmap.cube })
|
|
1025
1040
|
}
|
|
1026
1041
|
// navigation: load the baked mesh — or, under `lecodes navmesh bake`, dump the level's collision
|
|
1027
1042
|
// geometry for it (the static bodies exist by now; edit mode never loads it — v1)
|
package/src/version.ts
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
// same manifest). Its MAJOR is the bundle ↔ runtime contract: a bundle's `// sdk:` header line
|
|
5
5
|
// and a host's embedded version must agree on the major for the host to run the bundle; minor
|
|
6
6
|
// and patch never gate anything (features are detected, never versioned).
|
|
7
|
-
export const SDK_VERSION = "2.4.
|
|
7
|
+
export const SDK_VERSION = "2.4.7"
|
|
8
8
|
|
|
9
9
|
/** The major of a semver string, or null when it is not one. */
|
|
10
10
|
export const sdkMajor = (version: string | null | undefined): number | null => {
|