lecodes-sdk 2.4.7 → 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.
@@ -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) { _creator.addEntityToScene(this._id, n.id); nodes.pin(n) } // the scene owns it now
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/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"
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 => {