lecodes-sdk 2.4.6 → 2.4.8
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/global.d.ts +7 -0
- package/dist/types/gl/Camera.d.ts +100 -13
- package/dist/types/gl/Lightmap.d.ts +25 -4
- package/dist/types/gl/Model.d.ts +4 -4
- package/dist/types/gl/Node.d.ts +13 -0
- package/dist/types/gl/ReflectionProbe.d.ts +87 -0
- package/dist/types/gl/RenderTexture.d.ts +29 -0
- package/dist/types/gl/Scene.d.ts +21 -0
- package/dist/types/inject.d.ts +4 -0
- package/dist/types/scene/defineScene.d.ts +15 -2
- package/dist/types/ui/tree.d.ts +1 -0
- package/dist/types/version.d.ts +1 -1
- package/dist/types.json +1 -1
- package/package.json +1 -1
- package/src/bridges/gl.d.ts +72 -17
- package/src/gl/Camera.ts +198 -25
- package/src/gl/Lightmap.ts +127 -48
- package/src/gl/Model.ts +4 -4
- package/src/gl/Node.ts +61 -0
- package/src/gl/ReflectionProbe.ts +141 -0
- package/src/gl/RenderTexture.ts +93 -0
- package/src/gl/Scene.ts +54 -2
- package/src/inject.ts +4 -0
- package/src/math/vec.ts +1 -1
- package/src/scene/defineScene.ts +28 -13
- package/src/ui/tree.ts +1 -0
- package/src/version.ts +1 -1
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
// A texture a render view draws into (creator-gl renderview.cpp; creator.h createRenderTexture / createRenderView /
|
|
2
|
+
// setRenderViewOptions / setRenderViewProjection / readRenderTexturePixels). Only the texture: the view drawing into it
|
|
3
|
+
// belongs to what made it — a secondary Camera (`camera.renderToTexture`, a 2D picture) or a ReflectionProbe (a cube) —
|
|
4
|
+
// which holds the view's owned handle, its schedule and its projection. The helpers below are those two makers' shared
|
|
5
|
+
// half: the host's texture, and the view started once their node is in a scene (Node.ts awaitScene).
|
|
6
|
+
|
|
7
|
+
import { Texture } from "./Texture"
|
|
8
|
+
|
|
9
|
+
/** @internal `_creator.createRenderTexture` flags (creator.h CREATOR_RENDER_TEXTURE_*). */
|
|
10
|
+
export const RT_FLAG_CUBEMAP = 1
|
|
11
|
+
/** @internal */
|
|
12
|
+
export const RT_FLAG_HDR = 2
|
|
13
|
+
/** @internal */
|
|
14
|
+
export const RT_FLAG_PREFILTER = 4
|
|
15
|
+
/** @internal `_creator.createRenderView` flags bit: no shadow maps in this view (creator.h CREATOR_RENDER_VIEW_SHADOWS_OFF). */
|
|
16
|
+
const VIEW_FLAG_SHADOWS_OFF = 1
|
|
17
|
+
/** @internal The longest side the host takes (creator.h createRenderTexture). */
|
|
18
|
+
export const RT_MAX_SIDE = 4096
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* A texture a render view draws into — made by `camera.renderToTexture({ width, height })` (a 2D picture: the
|
|
22
|
+
* tone-mapped image as the screen would show it) or by a `ReflectionProbe` (`probe.texture`: a cube of linear HDR light).
|
|
23
|
+
* It IS a `Texture`: a 2D one goes wherever an image does (`material.map` — a monitor, a rear-view mirror), a cube
|
|
24
|
+
* wherever a cubemap does (`scene.environment` takes the probe itself).
|
|
25
|
+
*
|
|
26
|
+
* ROW ORDER: a 2D render texture's first texel row is the BOTTOM of the camera's picture (OpenGL's order, on every
|
|
27
|
+
* backend) — an image's first row is its top. A mesh whose v runs UP its face (`Mesh.plane`) shows it upright as is; one
|
|
28
|
+
* laid out like a glTF (v down) samples it with v flipped. A cube face is laid out as every GPU's cube lookup reads it.
|
|
29
|
+
*
|
|
30
|
+
* LIFETIME: the texture lives while something holds it — the camera or probe that made it (each keeps its texture), a
|
|
31
|
+
* material showing it — and keeps its last picture when its view stops (`enabled = false`, the camera's next
|
|
32
|
+
* `renderToTexture`, the camera gone).
|
|
33
|
+
*/
|
|
34
|
+
export class RenderTexture extends Texture {
|
|
35
|
+
/** A cube (a `ReflectionProbe`'s) rather than a 2D picture (a `Camera`'s). A cube's `width` and `height` are both its
|
|
36
|
+
* edge. */
|
|
37
|
+
readonly cubemap: boolean
|
|
38
|
+
|
|
39
|
+
/** @internal Made by `Camera.renderToTexture` and `ReflectionProbe` (through `makeRenderTexture`): the size, the
|
|
40
|
+
* kind and the texture's owned handle. */
|
|
41
|
+
constructor(width: number, height: number, cubemap: boolean, texture: Handle) {
|
|
42
|
+
super(width, height, texture)
|
|
43
|
+
this.cubemap = cubemap
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* RGBA8 bytes (`width × height × 4`) of a rectangle of the texture as the NEXT frame drawn leaves it — a GPU
|
|
48
|
+
* readback, for checks and tools, not a per-frame path. Rows come in the texture's own order: the first is texel row
|
|
49
|
+
* `y`, which for a 2D texture counts from the picture's BOTTOM. `face` 0..5 = +X −X +Y −Y +Z −Z of a cube (0 for a 2D
|
|
50
|
+
* picture). A camera's (LDR) texture gives its stored sRGB bytes, a probe's (HDR) its linear light clamped to 0..1 —
|
|
51
|
+
* a prefiltered probe its sharpest level. Rejects on a host without the call, or when the host refuses the read (the
|
|
52
|
+
* rectangle outside the texture, the engine gone first).
|
|
53
|
+
*/
|
|
54
|
+
readPixels(x: number, y: number, width: number, height: number, face = 0): Promise<Uint8Array> {
|
|
55
|
+
if (!_creator.readRenderTexturePixels) return Promise.reject(new Error("RenderTexture.readPixels: this host has no readRenderTexturePixels"))
|
|
56
|
+
return new Promise<Uint8Array>((resolve, reject) => {
|
|
57
|
+
_creator.readRenderTexturePixels!(this._h.id, face, x, y, width, height, (pixels) => {
|
|
58
|
+
if (pixels) resolve(pixels)
|
|
59
|
+
else reject(new Error(`RenderTexture.readPixels: the host refused (${x}, ${y}) ${width}×${height} of face ${face}`))
|
|
60
|
+
})
|
|
61
|
+
})
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/** @internal Throw for `who` on a host without render textures (createRenderTexture + createRenderView). */
|
|
66
|
+
export const requireRenderTextures = (who: string): void => {
|
|
67
|
+
if (!_creator.createRenderTexture || !_creator.createRenderView) {
|
|
68
|
+
throw new Error(`${who}: this host has no render textures (createRenderTexture) — the web hosts do not yet`)
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/** @internal The host's render texture (`flags` RT_FLAG_*; a cube's `height` is its `width`), or a throw naming `who`. */
|
|
73
|
+
export const makeRenderTexture = (who: string, width: number, height: number, flags: number, samples = 0): RenderTexture => {
|
|
74
|
+
requireRenderTextures(who)
|
|
75
|
+
const cube = (flags & RT_FLAG_CUBEMAP) !== 0
|
|
76
|
+
const texture = _creator.createRenderTexture!(width, cube ? 0 : height, flags, samples)
|
|
77
|
+
if (!texture || texture.id === 0xFFFFFFFF) {
|
|
78
|
+
throw new Error(`${who}: the host refused a ${cube ? `cube of ${width}` : `${width}×${height}`} render texture (sides 1..${RT_MAX_SIDE})`)
|
|
79
|
+
}
|
|
80
|
+
return new RenderTexture(width, cube ? width : height, cube, texture)
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/** @internal Start a render view of scene `sceneId` into `texture` from entity `poseId` (createRenderView), or warn for
|
|
84
|
+
* `who` and give null — the host refuses an overlay scene. */
|
|
85
|
+
export const startRenderView = (
|
|
86
|
+
who: string, sceneId: number, texture: RenderTexture, poseId: number,
|
|
87
|
+
layers: number, fov: number, near: number, far: number, shadows: boolean,
|
|
88
|
+
): Handle | null => {
|
|
89
|
+
const view = _creator.createRenderView!(sceneId, texture._h.id, poseId, layers & 0xff, fov, near, far, shadows ? 0 : VIEW_FLAG_SHADOWS_OFF)
|
|
90
|
+
if (view && view.id !== 0) return view
|
|
91
|
+
console.warn(`[${who}] the host made no render view in scene ${sceneId} (an overlay scene has none) — its texture stays empty`)
|
|
92
|
+
return null
|
|
93
|
+
}
|
package/src/gl/Scene.ts
CHANGED
|
@@ -18,8 +18,9 @@ import { Camera } from "./Camera"
|
|
|
18
18
|
import { SceneAudio } from "./audio/SceneAudio"
|
|
19
19
|
import { Material } from "./Material"
|
|
20
20
|
import { PostProcessing, type PostProcessingOptions } from "./postProcessing"
|
|
21
|
+
import { ReflectionProbe } from "./ReflectionProbe"
|
|
21
22
|
import { Texture } from "./Texture"
|
|
22
|
-
import { Node, nodes } from "./Node"
|
|
23
|
+
import { Node, bindPendingViews, nodes, sceneIdOf } from "./Node"
|
|
23
24
|
import { glState } from "./state"
|
|
24
25
|
import { registerTouchEndEvent, registerTouchStartEvent } from "./touch"
|
|
25
26
|
|
|
@@ -176,6 +177,12 @@ export class Scene implements Presentable {
|
|
|
176
177
|
/** @internal what `environmentIntensity` currently asks for — the accessor's backing field, so it
|
|
177
178
|
* answers on hosts whose engine has no live setter too. */
|
|
178
179
|
_environmentIntensity = 20000
|
|
180
|
+
/** @internal The probe the constructor named for `setDefaultIbl` (-1 = none: the project-wide
|
|
181
|
+
* `ibl.ktx`) — re-sent when `environment` goes back to null. */
|
|
182
|
+
_iblId = -1
|
|
183
|
+
/** @internal What `environment` lights the scene from (null = the scene's own probe). Holding it
|
|
184
|
+
* keeps the probe — its cube and the view drawing into it — alive while it lights. */
|
|
185
|
+
_environment: ReflectionProbe | null = null
|
|
179
186
|
readonly camera: Camera
|
|
180
187
|
/** The listener + global 3D audio knobs (docs/audio-plan.md). */
|
|
181
188
|
readonly audio: SceneAudio = new SceneAudio()
|
|
@@ -226,6 +233,7 @@ export class Scene implements Presentable {
|
|
|
226
233
|
// "no probe named", which leaves the host on the project-wide `ibl.ktx` (the old behaviour).
|
|
227
234
|
const src = typeof options.ibl === "string" ? options.ibl : undefined
|
|
228
235
|
const iblId = src === undefined ? -1 : src.startsWith("id:") ? Number(src.slice(3)) : _creatorFetch.local(src)
|
|
236
|
+
this._iblId = iblId
|
|
229
237
|
_creator.setDefaultIbl(this._id, options.environmentIntensity ?? 20000, iblId)
|
|
230
238
|
}
|
|
231
239
|
this._environmentIntensity = options.ibl === false ? 0 : (options.environmentIntensity ?? 20000)
|
|
@@ -341,6 +349,44 @@ export class Scene implements Presentable {
|
|
|
341
349
|
}
|
|
342
350
|
}
|
|
343
351
|
private static _warnedEnvIntensity = false
|
|
352
|
+
|
|
353
|
+
/**
|
|
354
|
+
* Light the scene from a live REFLECTION PROBE (ReflectionProbe.ts) — a cube drawn around the probe's position, the
|
|
355
|
+
* probe riding the car / standing in the room: chrome and glass show what is actually around them, moving objects
|
|
356
|
+
* included. `null` gives the light back to the scene's own probe (`SceneOptions.ibl`, at `environmentIntensity`;
|
|
357
|
+
* re-installing it reloads the probe — a switch, not a per-frame call). A probe in no scene yet starts its view in this
|
|
358
|
+
* one; one already in another scene draws only while that scene is on show.
|
|
359
|
+
*
|
|
360
|
+
* Prefiltered (the default), the probe gives the reflections at every roughness — a car body, painted metal blur it as
|
|
361
|
+
* they should — and the diffuse ambient too (then `environmentIntensity` no longer changes it); it costs a GPU
|
|
362
|
+
* filtering per completed cube (`ReflectionProbeOptions.prefilter`). `prefilter: false` gives one sharp level:
|
|
363
|
+
* mirror-like surfaces (roughness near 0) are right, rougher ones read the sharp cube too, and only the reflections
|
|
364
|
+
* change — the diffuse ambient keeps the scene's own probe's shape at `environmentIntensity`.
|
|
365
|
+
*
|
|
366
|
+
* A probe that draws while it lights reads what it writes — fine on OpenGL; a Vulkan backend may want two probes in
|
|
367
|
+
* turn (a prefiltered probe never does: it lights from the filtered copy). Leave the probe's own carrier out of its view
|
|
368
|
+
* (`layers`), or the cube is the inside of the car. Warns once on a host without the call.
|
|
369
|
+
*/
|
|
370
|
+
get environment(): ReflectionProbe | null { return this._environment }
|
|
371
|
+
set environment(probe: ReflectionProbe | null) {
|
|
372
|
+
if (probe !== null && !(probe instanceof ReflectionProbe)) {
|
|
373
|
+
throw new Error("scene.environment: a ReflectionProbe (new ReflectionProbe({ size: 128 })) or null")
|
|
374
|
+
}
|
|
375
|
+
if (probe === this._environment) return
|
|
376
|
+
this._environment = probe
|
|
377
|
+
if (probe && sceneIdOf(probe) < 0) probe._bindView(this._id) // in no scene yet: its view starts in this one
|
|
378
|
+
if (!_creator.setEnvironmentTexture) {
|
|
379
|
+
if (!Scene._warnedEnvTexture) {
|
|
380
|
+
Scene._warnedEnvTexture = true
|
|
381
|
+
console.warn("[scene] this host has no setEnvironmentTexture — environment stays the scene's own probe")
|
|
382
|
+
}
|
|
383
|
+
return
|
|
384
|
+
}
|
|
385
|
+
if (probe) _creator.setEnvironmentTexture(this._id, probe.texture._h.id)
|
|
386
|
+
else _creator.setDefaultIbl(this._id, this._environmentIntensity, this._iblId) // installing a probe frees the cube's light
|
|
387
|
+
}
|
|
388
|
+
private static _warnedEnvTexture = false
|
|
389
|
+
|
|
344
390
|
/** The scene's stencil buffer on / off (see `SceneOptions.stencil`). */
|
|
345
391
|
setStencil(enabled: boolean): void {
|
|
346
392
|
if (_creator.setSceneStencil) _creator.setSceneStencil(this._id, enabled)
|
|
@@ -348,12 +394,18 @@ export class Scene implements Presentable {
|
|
|
348
394
|
}
|
|
349
395
|
|
|
350
396
|
add(...list: Node[]): this {
|
|
351
|
-
for (const n of list) {
|
|
397
|
+
for (const n of list) {
|
|
398
|
+
_creator.addEntityToScene(this._id, n.id)
|
|
399
|
+
nodes.pin(n) // the scene owns it now
|
|
400
|
+
n._inScene = this._id // what a render view under it starts in (Node.ts sceneIdOf)
|
|
401
|
+
}
|
|
402
|
+
bindPendingViews()
|
|
352
403
|
return this
|
|
353
404
|
}
|
|
354
405
|
remove(...list: Node[]): this {
|
|
355
406
|
for (const n of list) {
|
|
356
407
|
_creator.removeEntityFromScene(this._id, n.id)
|
|
408
|
+
if (n._inScene === this._id) n._inScene = -1
|
|
357
409
|
if (!_creator.entityIsOwned(n.id)) nodes.unpin(n.id) // loose again: its handle owns it
|
|
358
410
|
}
|
|
359
411
|
return this
|
package/src/inject.ts
CHANGED
|
@@ -251,7 +251,11 @@ export type { ScatterPlacement, ScatterOptions } from "./gl/Scatter"
|
|
|
251
251
|
export type { InstanceTransform } from "./gl/InstancedMesh"
|
|
252
252
|
export { Texture } from "./gl/Texture"
|
|
253
253
|
export type { TextureLoadOptions } from "./gl/Texture"
|
|
254
|
+
export { RenderTexture } from "./gl/RenderTexture"
|
|
254
255
|
export { Camera } from "./gl/Camera"
|
|
256
|
+
export type { CameraOptions, RenderToTextureOptions } from "./gl/Camera"
|
|
257
|
+
export { ReflectionProbe } from "./gl/ReflectionProbe"
|
|
258
|
+
export type { ReflectionProbeOptions } from "./gl/ReflectionProbe"
|
|
255
259
|
export { Ray } from "./gl/Ray"
|
|
256
260
|
export { Plane } from "./gl/Plane"
|
|
257
261
|
export { Noise } from "./gl/Noise"
|
package/src/math/vec.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
// Fluent 2D / 3D vectors — the SDK's user-facing vector math (no gl-matrix, no out-params, no imports).
|
|
2
2
|
//
|
|
3
|
-
// Design
|
|
3
|
+
// Design: a vector is a *mutable little struct* — `v.x = 3`, `v.set(x, y, z)` mutate in
|
|
4
4
|
// place — but every *method* is PURE: `a.add(b)` returns a NEW vector and never touches `a`. So chains
|
|
5
5
|
// (`dir.normalize().scale(s)`) can't clobber their source, and the immutable-method style is the most
|
|
6
6
|
// amenable to a future escape-analysis / scalar-replacement compiler pass. Backing is plain number
|
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/ui/tree.ts
CHANGED
|
@@ -93,6 +93,7 @@ export const OWNED_KIND_ENTITY_2D = 5
|
|
|
93
93
|
export const OWNED_KIND_MEDIA = 6
|
|
94
94
|
export const OWNED_KIND_TWEEN = 7
|
|
95
95
|
export const OWNED_KIND_CANVAS = 8
|
|
96
|
+
export const OWNED_KIND_GL_RENDER_VIEW = 9
|
|
96
97
|
|
|
97
98
|
// PanDirection (creator-pkg.h) — the low byte of a track answer.
|
|
98
99
|
import { DEAD_HANDLE } from "../core/pins"
|
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.8"
|
|
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 => {
|