lecodes-sdk 2.4.7 → 2.4.9

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 CHANGED
@@ -102,6 +102,8 @@ declare global {
102
102
  const RTCVideoView: typeof SDK.RTCVideoView
103
103
  const Ragdoll: typeof SDK.Ragdoll
104
104
  const Ray: typeof SDK.Ray
105
+ const ReflectionProbe: typeof SDK.ReflectionProbe
106
+ const RenderTexture: typeof SDK.RenderTexture
105
107
  const Replicated: typeof SDK.Replicated
106
108
  const Router: typeof SDK.Router
107
109
  const RpcError: typeof SDK.RpcError
@@ -293,6 +295,7 @@ declare global {
293
295
  type CameraFollow = SDK.CameraFollow
294
296
  type CameraMove = SDK.CameraMove
295
297
  type CameraNodeDef = SDK.CameraNodeDef
298
+ type CameraOptions = SDK.CameraOptions
296
299
  type CameraPlace = SDK.CameraPlace
297
300
  type CameraView = SDK.CameraView
298
301
  type Canvas = SDK.Canvas
@@ -387,6 +390,10 @@ declare global {
387
390
  type Light = SDK.Light
388
391
  type LightDef = SDK.LightDef
389
392
  type Lightmap = SDK.Lightmap
393
+ type LightmapDef = SDK.LightmapDef
394
+ type LightmapFiles = SDK.LightmapFiles
395
+ type LightmapOptions = SDK.LightmapOptions
396
+ type LightmapTransmit = SDK.LightmapTransmit
390
397
  type LineLayer = SDK.LineLayer
391
398
  type LineLayerOptions = SDK.LineLayerOptions
392
399
  type LitMaterialDef = SDK.LitMaterialDef
@@ -521,6 +528,10 @@ declare global {
521
528
  type RecordOptions = SDK.RecordOptions
522
529
  type RecorderState = SDK.RecorderState
523
530
  type Recording = SDK.Recording
531
+ type ReflectionProbe = SDK.ReflectionProbe
532
+ type ReflectionProbeOptions = SDK.ReflectionProbeOptions
533
+ type RenderTexture = SDK.RenderTexture
534
+ type RenderToTextureOptions = SDK.RenderToTextureOptions
524
535
  type Replicated = SDK.Replicated
525
536
  type Resource<T> = SDK.Resource<T>
526
537
  type ReverbParams = SDK.ReverbParams
package/dist/host.d.ts CHANGED
@@ -1,9 +1,11 @@
1
1
  // JS-runtime globals provided by the host engine (web viewer / desktop / iOS), NOT by the SDK.
2
2
  // Declared only so SDK and user code type-check — never bundled or injected.
3
3
 
4
- // The one SDK type the macros return: an SVG image source, the same `SvgSource` a project has as
5
- // a global (build-types.ts points this import into dist/types when it copies the file).
4
+ // The SDK types the macros return: an SVG image source, the same `SvgSource` a project has as a
5
+ // global, and the lightmap def `bake()` compiles to (build-types.ts points these imports into
6
+ // dist/types when it copies the file).
6
7
  import type { SvgSource } from "./types/runtime/misc"
8
+ import type { LightmapDef, LightmapOptions } from "./types/gl/Lightmap"
7
9
 
8
10
  declare global {
9
11
  /** As in the browser, but the delay is REQUIRED (the native runtime throws without it) and no
@@ -50,6 +52,15 @@ declare global {
50
52
  * comments and string literals are text, not macros. */
51
53
  function asset(path: string): string
52
54
 
55
+ /** THE LEVEL'S BAKE: `lecodes lightmap bake` writes `<stem>.bake` (a JSON manifest) and the files it names next to
56
+ * it. Compile-time macro: the manifest is read, every file it names is staged, and the call becomes the literal
57
+ * `Lightmap.load` takes — `env: { lightmap: bake('./assets/lightmap/level.bake') }` in a scene file. The path is a
58
+ * string literal, relative to the file that calls it (`./`, `../`), else to the project root. No file yet →
59
+ * `{ data: null, path }`: the level runs unbaked and says so; under `lecodes lightmap bake` this is the target the
60
+ * bake writes. A file the manifest names that is missing fails the compile. `options` (an object literal, copied
61
+ * as written) are the level's bake / load options. */
62
+ function bake(path: `${string}.bake`, options?: LightmapOptions): LightmapDef
63
+
53
64
  /** Compile-time macro: installs a font and returns its family name — using a font IS declaring
54
65
  * it. A registry id (`font("manrope")`) resolves against the font registry; a project path
55
66
  * (`font("./fonts/Brand.ttf")`) reads family/weight/italic from the file itself. `opts`
@@ -1,26 +1,61 @@
1
1
  import { Vec3 } from "../math/vec";
2
2
  import { Node, type NodeTweenProps } from "./Node";
3
3
  import { Ray } from "./Ray";
4
+ import { RenderTexture } from "./RenderTexture";
4
5
  import { type TweenMeta } from "../animate/tween/spec";
5
6
  import type { Animation } from "../animate/tween/Animation";
6
- /** Animatable camera props on top of the node transform. */
7
+ /** Animatable camera props on top of the node transform. `fov` / `near` / `far` are the SCENE camera's: on a secondary
8
+ * camera they throw (set them directly — they are live — or from `animate({ onUpdate })`). */
7
9
  export type CameraTweenProps = NodeTweenProps & {
8
10
  fov?: number | number[];
9
11
  near?: number | number[];
10
12
  far?: number | number[];
11
13
  };
14
+ /** `new Camera(options)`: a SECONDARY camera's lens and what its view draws (the scene's own camera is `scene.camera`). */
15
+ export type CameraOptions = {
16
+ /** Vertical field of view in degrees (default 60). Live: `camera.fov`. */
17
+ fov?: number;
18
+ /** Near clip distance in metres (default 0.01). Live: `camera.near`. */
19
+ near?: number;
20
+ /** Far clip distance in metres (default 1000). Live: `camera.far`. */
21
+ far?: number;
22
+ /** The layers its view draws, a bit mask over `Node.layer` (default 0xff = everything): a renderable is drawn when its
23
+ * layers share a bit with these — the player's own body left out of a mirror. Fixed when the view starts. */
24
+ layers?: number;
25
+ /** Shadows in its view (default `true`): the view renders shadow maps of its own each time it draws. Fixed when the
26
+ * view starts. */
27
+ shadows?: boolean;
28
+ };
29
+ /** `camera.renderToTexture(options)`: the picture's size. */
30
+ export type RenderToTextureOptions = {
31
+ /** Texels wide, 1..4096 (default 256). */
32
+ width?: number;
33
+ /** Texels tall, 1..4096 (default 256). */
34
+ height?: number;
35
+ };
12
36
  /** Projection every host starts a scene with (creator-gl scene.cpp / the lite core agree on these). */
13
37
  export declare const CAMERA_DEFAULTS: {
14
38
  fov: number;
15
39
  near: number;
16
40
  far: number;
17
41
  };
42
+ /**
43
+ * A camera: a Node (position, `lookAt`, parenting — it looks down its −Z) with a lens, `fov` / `near` / `far`. Two kinds:
44
+ * - THE SCENE CAMERA, `scene.camera`: made by the Scene, it draws the screen. Its exposure, `projectionMatrix` and the
45
+ * ray helpers (`getRay`, `getViewDirection`) are the screen's, and its projection tweens (`animateTo({ fov })`).
46
+ * - A SECONDARY camera, `new Camera({ fov, layers, shadows })`: a plain node until `renderToTexture({ width, height })`
47
+ * gives it a second view of its scene, drawn into a texture every frame the scene is on show — a rear-view mirror, a
48
+ * monitor, a security camera. Place it like any node (`car.add(mirrorCam)`); its lens is live (`mirrorCam.fov = 50`),
49
+ * its view's schedule is `enabled` / `everyNth`. A cube of light for reflections is a `ReflectionProbe`.
50
+ */
18
51
  export declare class Camera extends Node {
52
+ /** The scene this camera draws the screen of (Scene `_attach`); -1 = a secondary camera. */
19
53
  private _sceneId;
20
- /** Tween `fov` / `near` / `far` (a zoom, a dolly) and the transform — see {@link Node.animateTo}. */
54
+ /** Tween `fov` / `near` / `far` (a zoom, a dolly — the scene camera's) and the transform — see {@link Node.animateTo}. */
21
55
  animateTo(props: CameraTweenProps & TweenMeta): Animation;
22
56
  animateFrom(props: CameraTweenProps & TweenMeta): Animation;
23
- /** Live projection matrix (updated by the host when the viewport/fov changes). */
57
+ /** The scene camera's live projection matrix (updated by the host when the viewport / fov changes). A secondary
58
+ * camera's stays zero: its projection is `fov` / `near` / `far` at its texture's aspect. */
24
59
  readonly projectionMatrix: Float32Array<ArrayBuffer>;
25
60
  private _fov;
26
61
  private _near;
@@ -31,11 +66,54 @@ export declare class Camera extends Node {
31
66
  private _exposure;
32
67
  private _ev;
33
68
  private _exposureSet;
69
+ private readonly _layers;
70
+ private readonly _shadows;
71
+ private _enabled;
72
+ private _everyNth;
73
+ /** A SECONDARY camera (`scene.camera` is the scene's own): a plain node until `renderToTexture`. `options`: its lens
74
+ * (live after, through `fov` / `near` / `far`) and what its view draws (`layers`, `shadows` — fixed). */
75
+ constructor(options?: CameraOptions);
76
+ /**
77
+ * Draw this camera's view of its scene into a new 2D texture of `width × height` texels (default 256 × 256): the
78
+ * tone-mapped picture as the screen would show it (LDR) — `screen.material.map = cam.renderToTexture({ ... })`. The
79
+ * camera holds it (`camera.texture`).
80
+ *
81
+ * THE VIEW starts once the camera is in a scene (added to one, or under a node that is — at once if it already is)
82
+ * and draws every frame that scene is on show, after the frame's update passes (aspects, physics, animation: this
83
+ * frame's poses) and before the scene's own picture. It sees the scene's renderables, lights, environment and sky (not
84
+ * its overlays) from the camera's WORLD pose (scale ignored) through `fov` / `near` / `far` at the texture's aspect,
85
+ * with the `layers` and `shadows` the camera was made with, at the scene camera's exposure; its post-processing is the
86
+ * engine's default tone mapping (the scene's grading, bloom and the rest are not mirrored). Moving the camera to
87
+ * another scene later does not move the view.
88
+ *
89
+ * COST: every render is another pass over the scene — with `shadows`, its own shadow maps too. Keep the texture small,
90
+ * draw less often (`everyNth`), leave out what the view does not need (`layers`), and turn it off out of sight
91
+ * (`enabled = false`).
92
+ *
93
+ * Calling it again REPLACES the texture and the view: the previous view stops at once, its texture keeps its last
94
+ * picture while something still shows it. Throws on the scene camera, and on a host without render textures (the web
95
+ * hosts, for now).
96
+ */
97
+ renderToTexture(options?: RenderToTextureOptions): RenderTexture;
98
+ /** The texture `renderToTexture` made — the latest, null before one. */
99
+ get texture(): RenderTexture | null;
100
+ /** Whether this secondary camera's view draws (default `true`). Off keeps the last picture in its texture and costs
101
+ * nothing — a mirror out of sight; back on, it renders on the next frame drawn. The scene camera always draws (reads
102
+ * `true`; setting it throws). */
103
+ get enabled(): boolean;
104
+ set enabled(on: boolean);
105
+ /** Render this secondary camera's view once every N frames DRAWN (default 1 = every frame). The count restarts with
106
+ * each change: the next frame drawn renders, then one in every N. The scene camera draws every frame (reads 1;
107
+ * setting it throws). */
108
+ get everyNth(): number;
109
+ set everyNth(n: number);
34
110
  /** Exposure compensation in stops (default 0): +1 doubles how bright the scene renders, −2
35
111
  * quarters it. The one exposure knob most games need — a dark interior, a flash of white, or eye
36
112
  * adaptation when the sun swings into view (drop it, and the sun stays at the display's peak
37
113
  * while everything else darkens). Scene light follows it; particle `emissive` is post-exposure
38
- * and does not, so a fireball keeps its on-screen brightness. Cheap to animate every frame. */
114
+ * and does not, so a fireball keeps its on-screen brightness. Cheap to animate every frame. The
115
+ * scene camera's: a secondary camera's view renders at its scene camera's exposure (what is set on
116
+ * a secondary camera is kept, unused). */
39
117
  /** The camera's EV100 with the compensation in: log2(aperture² / shutterSpeed x 100 / iso) - compensation. Sunny 16 is
40
118
  * ~15; a lit surface of luminance L lands on screen at L / (1.2 x 2^ev100). */
41
119
  get ev100(): number;
@@ -43,7 +121,7 @@ export declare class Camera extends Node {
43
121
  set exposureCompensation(stops: number);
44
122
  /** The physical camera: `aperture` in f-stops, `shutterSpeed` in seconds, `iso`, plus the same
45
123
  * `compensation` in stops — any subset, one call. `camera.setExposure({ iso: 400 })` is two stops
46
- * brighter than the default f/16 · 1/125 · ISO 100. */
124
+ * brighter than the default f/16 · 1/125 · ISO 100. The scene camera's (as `exposureCompensation`). */
47
125
  setExposure(exposure: {
48
126
  aperture?: number;
49
127
  shutterSpeed?: number;
@@ -52,15 +130,18 @@ export declare class Camera extends Node {
52
130
  }): this;
53
131
  /** Hosts that predate the call keep the fixed default (older wasm / native builds). */
54
132
  private _applyExposure;
55
- /** Vertical field of view in degrees (default 60) — a smaller angle is a longer lens. */
133
+ /** Vertical field of view in degrees (default 60) — a smaller angle is a longer lens. Live on both kinds: a
134
+ * secondary camera's reaches its view at the next render (before `renderToTexture`, the view's starting value). */
56
135
  get fov(): number;
57
136
  set fov(degrees: number);
137
+ /** The horizontal field of view in degrees: the scene camera's at the screen's aspect, a secondary camera's at its
138
+ * texture's (square before `renderToTexture`). */
58
139
  get horizontalFov(): number;
59
- /** Near clip distance (default 0.01) — nothing closer than this draws. */
140
+ /** Near clip distance (default 0.01) — nothing closer than this draws. Live, as `fov`. */
60
141
  get near(): number;
61
142
  set near(distance: number);
62
143
  /** Far clip distance / view range (default 1000) — geometry past it is culled. Shadows keep
63
- * their own (shorter) range, so a long view distance doesn't cost shadow sharpness. */
144
+ * their own (shorter) range, so a long view distance doesn't cost shadow sharpness. Live, as `fov`. */
64
145
  get far(): number;
65
146
  set far(distance: number);
66
147
  /** Set any of fov / near / far in one call: `camera.setProjection({ fov: 45, far: 5000 })`. */
@@ -69,8 +150,10 @@ export declare class Camera extends Node {
69
150
  near?: number;
70
151
  far?: number;
71
152
  }): this;
72
- /** Hosts that predate the call keep the fixed defaults (older wasm / native builds). */
153
+ /** Hosts that predate the call keep the fixed defaults (older wasm / native builds); a secondary camera's view keeps
154
+ * the projection it started with (warned once). */
73
155
  private _applyProjection;
156
+ private static _warnedViewProjection;
74
157
  private _following;
75
158
  /** The node the camera currently follows (`follow()`), or null. */
76
159
  get following(): Node | null;
@@ -79,7 +162,9 @@ export declare class Camera extends Node {
79
162
  * view direction), so movement aspects / animation on that node are camera moves — dolly shots,
80
163
  * a cutscene path, a `CameraPlace` in a scene file. A node carrying a `CameraPlace` also hands
81
164
  * over its projection (fov / near / far). `follow(null)` releases the camera where it is.
82
- * One camera per scene: following a new node stops following the previous one.
165
+ * Following a new node stops following the previous one, and a node leads one camera at a time.
166
+ * Either kind: the pose is written as the camera's own transform, so a following secondary
167
+ * camera is a scene root, not a child.
83
168
  */
84
169
  follow(target: Node | {
85
170
  node: Node;
@@ -87,10 +172,12 @@ export declare class Camera extends Node {
87
172
  } | null): this;
88
173
  /** The scene view's [width, height] in logical units — the units of the scene's touches
89
174
  * (`clientX` / `clientY`) and of `getRay`, on every host; × `device.pixelRatio` for device px.
90
- * [0, 0] before the scene has a viewport. */
175
+ * [0, 0] before the scene has a viewport. The screen's, whichever camera reads it. */
91
176
  get displaySize(): [number, number];
92
- /** World-space ray direction through a point of the scene view (logical units, as a touch's). */
177
+ /** World-space ray direction through a point of the scene view (logical units, as a touch's). The scene camera's
178
+ * (a secondary camera throws). */
93
179
  getViewDirection(screenX: number, screenY: number): Vec3;
94
- /** A pick ray from the camera through a point of the scene view (logical units, as a touch's). */
180
+ /** A pick ray from the camera through a point of the scene view (logical units, as a touch's). The scene camera's
181
+ * (a secondary camera throws). */
95
182
  getRay(screenX: number, screenY: number): Ray;
96
183
  }
@@ -92,6 +92,11 @@ export type SpotOptions = {
92
92
  innerAngle?: number;
93
93
  /** A spot's shadow is one 2D shadow map (a point light's is a cubemap); off by default. */
94
94
  castShadows?: boolean;
95
+ /**
96
+ * Where the cone points, as a sun's `direction`: it sets the node's rotation (live as `light.direction`). Default:
97
+ * the node's forward (-Z) - aim it later with `lookAt` / `eulerAngles`.
98
+ */
99
+ direction?: Vec3Like;
95
100
  };
96
101
  export declare class Light extends Node {
97
102
  private _intensity;
@@ -111,8 +116,8 @@ export declare class Light extends Node {
111
116
  _innerAngle: number;
112
117
  /**
113
118
  * A spot light — a flashlight, a headlight, a stage light. It shines along the node's forward (-Z): position
114
- * it like any node and aim it with `lookAt`. Real time only: the lightmap bake takes no spot lights, so a spot
115
- * lights the baked statics live too.
119
+ * it like any node and aim it with `direction` or `lookAt`. Real time only: the lightmap bake takes no spot lights,
120
+ * so a spot lights the baked statics live too.
116
121
  */
117
122
  static spot(options?: SpotOptions): Light;
118
123
  /** A spot's full cone angle in degrees — live (a flashlight's focus). */
@@ -1,15 +1,21 @@
1
1
  import { Node } from "./Node";
2
2
  import type { Scene } from "./Scene";
3
- export type LightmapLoadOptions = {
3
+ /** The level's bake / load options: `bake()`'s second argument (an object literal, copied into the def as written). */
4
+ export type LightmapOptions = {
4
5
  /** A DEBUG multiplier on the atlas' lux (default 1): the atlas is physical, so this is a knob for looking,
5
6
  * not a look. */
6
7
  lightScale?: number;
7
- /** The level's own radiance of emission 1.0 for the BAKE (cd / m², `BakeConfig.emissiveNits`): an imported pack's
8
- * emissive values are tuned for a look, not for light - this is where the level says how much its panels really give.
9
- * The CLI's `--emissive-nits` wins over it; without either the camera's exposure decides. */
8
+ /** FOR THE BAKE: the radiance in cd / m² of emission 1.0 on this level (emissive factor x strength x map) - every lit
9
+ * region of an emissive surface bakes as a rectangle lamp. An imported pack's emission is a LOOK, tuned for the eye;
10
+ * this is where the level says what its panels really give: a 1.3 m ceiling panel of ~8000 lm is ~1600 nits. Default:
11
+ * what the camera makes of 1.0 (`1.2 x 2^EV100`). 0 = emissive surfaces light nothing. The CLI's `--emissive-nits`
12
+ * wins over it. A `Material.lit` glow with `emissiveNits` of its own emits those instead. */
10
13
  emissiveNits?: number;
11
- /** `false` = the emissive surfaces light NOTHING in the bake (the level is lit by its lamps, the panels are decoration)
12
- * while `emissiveNits` still says how bright their glow is drawn. Default true. */
14
+ /** `false` = the emissive surfaces light NOTHING in the bake - the level is lit by its lamps (a `kind: 'point'` light
15
+ * with a `bakeArea` next to every panel, the Unity packs' way) and the panels are decoration; `emissiveNits` then only
16
+ * says how bright their glow is DRAWN (scene-referred: nits through the camera's exposure). Default true. The
17
+ * OVERRIDE over every surface's own switch (`Material.lit`'s `emissiveBake`, a GLB material's extras
18
+ * `{"lecodes":{"emissiveBake":false}}`), which vetoes one material while the rest still light the level. */
13
19
  emissiveBake?: boolean;
14
20
  /** FOR THE BAKE: materials that let light THROUGH them, by their glTF name (`"name*"` = every name with that start):
15
21
  * the share of a shadow ray that passes, tinted by the material's base colour x map - an awning's warm, patterned light
@@ -24,7 +30,7 @@ export type LightmapLoadOptions = {
24
30
  * or charts. Added to the CLI's `--cube` patterns (both apply). */
25
31
  cube?: string[];
26
32
  };
27
- /** one `transmit` entry (LightmapLoadOptions.transmit): the straight share, or both shares */
33
+ /** one `transmit` entry (LightmapOptions.transmit): the straight share, or both shares */
28
34
  export type LightmapTransmit = number | {
29
35
  through?: number;
30
36
  diffuse?: number;
@@ -39,26 +45,34 @@ export type LightmapInfo = {
39
45
  total: number;
40
46
  missing: string[];
41
47
  };
48
+ /** The bake's files as `bake()` reads them off the `.bake` manifest at compile time — each one staged like an asset()
49
+ * (every name in the manifest is relative to the `.bake`'s folder). Written by the macro, never by hand. */
42
50
  export type LightmapFiles = {
43
- data: string;
44
- /** The irradiance atlas — one page, or every page in order for a bake that took several. */
45
- light: string | string[];
46
- /** The aux atlas (sun / sky visibility + light direction), pages like `light`. */
47
- aux: string | string[];
51
+ /** The `.bake` (the manifest) — null until `lecodes lightmap bake` has written it: the level then runs unbaked. */
52
+ data: string | null;
53
+ /** Project-relative path of the `.bake`, always set: what the messages name, and where `lecodes lightmap bake` writes. */
54
+ path: string;
55
+ /** The irradiance atlas (`<stem>-light[_n].ktx2`), every page in order. */
56
+ light: string[];
57
+ /** The aux atlas (`<stem>-aux[_n].ktx2`: sun / sky visibility + light direction), pages like `light`. */
58
+ aux: string[];
48
59
  /** DEBUG: the direct-light-only pages a `--split` bake wrote (`<stem>-direct[_n].ktx2`), for the "direct" view. */
49
- direct?: string | string[];
60
+ direct?: string[];
50
61
  /** DEBUG: the bounce-only pages of a `--split` bake (`<stem>-indirect[_n].ktx2`), for the "indirect" view. */
51
- indirect?: string | string[];
62
+ indirect?: string[];
52
63
  /** The reflection probes (`<stem>-probes.ktx2`), when the bake placed some (`lecodes lightmap bake --probe-spacing`). */
53
64
  probes?: string;
54
- /** THE LIGHT GRID for movers (`<stem>.lgrid`, `lecodes lightmap bake --volume`): the baked ambient light of every
55
- * place a mover can be. Absent = movers keep the scene's IBL. */
65
+ /** THE LIGHT GRID for movers (`<stem>.lgrid`, `lecodes lightmap bake --volume`): every node a Physics aspect or a
66
+ * CharacterController moves (and what code marks with `Lightmap.track`) takes its ambient light from the bake at the
67
+ * place it is at, whatever its shader. Absent = movers keep the scene's IBL. */
56
68
  volume?: string;
57
- /** THE PACKED LIGHTMAP UV1 (`<stem>-uv1.bin`, the .bake's `uv1`): the bake packs every mesh's lightmap charts into a
58
- * square of their own and its rects map that uv1 — the engine swaps it into the models' TEXCOORD_1 first. Required
59
- * when the bake names one. */
69
+ /** THE PACKED LIGHTMAP UV1 (`<stem>-uv1.bin`): the bake packs every mesh's lightmap charts into a square of their own
70
+ * and its rects map that uv1 — the engine swaps it into the models' TEXCOORD_1 before the rects go on. */
60
71
  uv1?: string;
61
72
  };
73
+ /** What `bake('<path>.bake', options)` compiles to — the files of the bake and the level's options in one literal:
74
+ * `env.lightmap` in a scene file, `Lightmap.load`'s argument in a hand-built scene. */
75
+ export type LightmapDef = LightmapFiles & LightmapOptions;
62
76
  /** The lightmap material's data views (`Lightmap.debug`); "direct" / "indirect" bind a `--split` bake's pages instead. */
63
77
  export type LightmapDebugMode = "off" | "irradiance" | "albedo" | "normal" | "shadingNormal" | "share" | "skyVis" | "atlas" | "direction" | "lux" | "relief" | "direct" | "indirect" | "probe" | "probeMap" | "skyPath" | "probePath";
64
78
  export type LightmapDebugParams = {
@@ -103,7 +117,7 @@ export declare class Lightmap {
103
117
  private static next;
104
118
  private static movers;
105
119
  private static volumeOn;
106
- /** A MOVER takes its ambient light from the level's baked LIGHT GRID (`env.lightmap.volume`) at the place it is at,
120
+ /** A MOVER takes its ambient light from the level's baked LIGHT GRID (the bake's `.lgrid`) at the place it is at,
107
121
  * every frame — whatever its shader: the standard glTF one or a custom lit material. Every Model / Mesh under `node`
108
122
  * is marked (a character, a weapon with its parts). A scene file does this itself for every node a Physics aspect or
109
123
  * a CharacterController moves; call it for what CODE spawns — an enemy, a pickup, a projectile. `on = false` hands
@@ -127,14 +141,15 @@ export declare class Lightmap {
127
141
  /** The loaded bake's sun (TO the sun ×3, its illuminance rgb, the sun map's levels), null without a sun map. */
128
142
  private static sunArgs;
129
143
  private static applySun;
130
- /** Apply a bake — or, under `lecodes lightmap bake`, run it. Resolves to null when nothing was
131
- * applied (no bake yet, a host without the feature, bake mode). */
144
+ /** Apply a bake — or, under `lecodes lightmap bake`, run it. `def` is what `bake('<path>.bake', options)` compiled
145
+ * to (a scene file's `env.lightmap`). Resolves to null when nothing was applied (no bake yet, a host without the
146
+ * feature, bake mode). */
132
147
  private static scene;
133
- static load(scene: Scene, files: LightmapFiles, options?: LightmapLoadOptions): Promise<LightmapInfo | null>;
148
+ static load(scene: Scene, def: LightmapDef): Promise<LightmapInfo | null>;
134
149
  /** The bake's `<key>@k` records by their node key: a model's meshes (k = the primitive's ordinal in its file). */
135
150
  private static recordsOf;
136
151
  /** Switch every lightmapped surface to a DATA view (or back with `"off"` / 0). Level-wide, instant —
137
- * except "direct" / "indirect", which load that page set the first time (a `--split` bake listed in the scene). */
152
+ * except "direct" / "indirect", which load that page set the first time (a bake made with `--split`). */
138
153
  static debug(mode: LightmapDebugMode | number, params?: LightmapDebugParams): void;
139
154
  /** Bind another page set of the bound bake to every surface (the "direct" / "indirect" views). */
140
155
  private static show;
@@ -152,7 +167,12 @@ export declare class Lightmap {
152
167
  static probe(screenX: number, screenY: number): LightmapProbe | null;
153
168
  /** A material's colour uniform as "#rrggbbaa" (the float4 uniform path sets its alpha); white without one. */
154
169
  private static colorUniform;
155
- /** The lightmap material for a static Mesh, from whatever lit material it carried (colour, map, roughness, metallic). */
170
+ /** The lightmap material for a static Mesh, from whatever lit material it carried (colour, map, roughness, metallic)
171
+ * — and its GLOW, so a glowing Mesh looks the same before and after the bake applies: `emissive` (linear) becomes the
172
+ * lightmap material's `emissiveFactor` at strength 1; its own `emissiveNits` go over with `emissiveOwn` 1, which the
173
+ * level-wide write (`lightmapSetOptions`) leaves alone. A glow without nits takes the level's, like a GLB panel:
174
+ * screen-referred under a level of 0 nits, else emission x the level's nits through the exposure — the same as
175
+ * before the bake when the level's nits are the camera's (the default). */
156
176
  private static meshMaterial;
157
177
  private static bake;
158
178
  }
@@ -112,12 +112,25 @@ export type ParticlesMaterialOptions = {
112
112
  * length = size + stretch·|velocity|. Default 0.05. */
113
113
  stretch?: number;
114
114
  };
115
- /** `Material.lit` options — PBR scalars on top of the color/map pair. */
115
+ /** `Material.lit` options — PBR scalars on top of the color/map pair, and the surface's own glow. */
116
116
  export type LitMaterialOptions = MaterialColorOptions & MaterialStateOptions & {
117
117
  /** Perceptual roughness, 0 (mirror) … 1 (matte). Unset = the shader's default. */
118
118
  roughness?: number;
119
119
  /** Metallic factor, 0 (dielectric) … 1 (metal). Unset = the shader's default. */
120
120
  metallic?: number;
121
+ /** The surface's GLOW, added on top of its lit colour: a ceiling panel, a screen, a sign. Any colour (sRGB, like
122
+ * `color`); an `[r, g, b]` array may go past 1 — an HDR colour, its hue in sRGB and the excess a linear multiplier
123
+ * (`[4, 3.6, 3]` = `[1, 0.9, 0.75]` four times as bright). Unset = no glow. */
124
+ emissive?: ColorInput;
125
+ /** The glow's luminance in cd / m² (nits) for `emissive` 1.0 — `#ffffff` at 1600 is a typical ceiling panel. Set,
126
+ * the glow is drawn SCENE-referred: through the camera's exposure, as bright as the light it gives the room in a
127
+ * bake. Unset (or 0) = SCREEN-referred: the colour is drawn as it is whatever the exposure, and a bake counts it at
128
+ * the level's `emissiveNits` (what the camera makes of 1.0 unless the level says otherwise). */
129
+ emissiveNits?: number;
130
+ /** `false` = decoration: the glow is drawn, but a lightmap bake does not light the level with it (Unity's "Global
131
+ * Illumination: None") — the level's lamps do. Default true: a glowing static lights the level like a lamp. The
132
+ * level-wide `bake('…', { emissiveBake: false })` overrides it for every surface. */
133
+ emissiveBake?: boolean;
121
134
  };
122
135
  export declare class Material {
123
136
  readonly shader: FetchResponse | "unknown";
@@ -139,7 +152,8 @@ export declare class Material {
139
152
  set map(value: Texture | Canvas | null);
140
153
  /** The material an instance handle is of; a host that carries none under `name` is an error naming it. */
141
154
  private static _builtin;
142
- /** PBR lit material. */
155
+ /** PBR lit material. `emissive` / `emissiveNits` / `emissiveBake` are its glow (`LitMaterialOptions`); the uniforms
156
+ * are written only for a material that asks for a glow, so a plain lit material never names them. */
143
157
  static lit(options?: LitMaterialOptions): Material;
144
158
  /** Unlit (flat) material — opaque unless `transparent` asks for alpha blending. */
145
159
  static unlit(options?: UnlitMaterialOptions): Material;
@@ -40,6 +40,19 @@ export declare class Node extends AspectHost<NodeEvents> implements CompWriter {
40
40
  set name(value: string);
41
41
  get visible(): boolean;
42
42
  set visible(value: boolean);
43
+ /**
44
+ * The node's visibility LAYERS, a bit mask 1..255. The scene's own view draws every layer; a render
45
+ * view (a secondary `Camera`'s or a `ReflectionProbe`'s, their `layers` option) draws only the
46
+ * renderables whose layers share a bit with its mask — which is what layers are for: leaving a subtree
47
+ * out of a second camera (the car out of the reflection probe riding on it, the player's own body out
48
+ * of a mirror). A node without a layer
49
+ * of its own takes its nearest ancestor's, else 1, so a car's layer covers everything parented under
50
+ * it; `0` clears the node's own (inherit again). `visible = false` wins over any layer. Reads the
51
+ * effective bits. A host without layers draws everything everywhere (reads 1, a write warns once).
52
+ */
53
+ get layer(): number;
54
+ set layer(mask: number);
55
+ private static _warnedLayer;
43
56
  private _sync;
44
57
  get matrix(): Mat4;
45
58
  set matrix(m: Mat4Like);
@@ -184,7 +184,7 @@ export type ParticlesOptions = ParticlesMaterialOptions & {
184
184
  * without this the engine picks who covers whom per frame (smoke popping over a fireball).
185
185
  * Default 0; a fireball wants 2, its smoke 1, a smoke trail -1. */
186
186
  order?: number;
187
- /** Sprites take the light of the PLACE each one is in — the level's baked light grid (`env.lightmap.volume`).
187
+ /** Sprites take the light of the PLACE each one is in — the level's baked light grid (the bake's `.lgrid`).
188
188
  * A sprite is unlit: its colour is the picture, which is right for fire and wrong for dust — a puff kicked up
189
189
  * under an awning glows as if it stood in the sun. With `lit` every particle's colour is multiplied by the light
190
190
  * where it is, relative to the level's OPEN ground: exactly the authored colour out in the sun, the ambient's
@@ -0,0 +1,87 @@
1
+ import { Node } from "./Node";
2
+ import { RenderTexture } from "./RenderTexture";
3
+ /** `new ReflectionProbe(options)`: the cube, what its view draws, and how often. */
4
+ export type ReflectionProbeOptions = {
5
+ /** The cube's edge, texels per face side (default 128): a power of two, 16..4096, when prefiltered; 1..4096 otherwise.
6
+ * A probe is light, not a picture — 64..128 is plenty for a car's reflections. */
7
+ size?: number;
8
+ /** The layers its view draws, a bit mask over `Node.layer` (default 0xff = everything): leave out the probe's own
9
+ * carrier, or the cube is the inside of the car it rides on. Fixed. */
10
+ layers?: number;
11
+ /** Shadows in its view (default `false`): with them, a full cube renders its own shadow maps six times. Fixed. */
12
+ shadows?: boolean;
13
+ /** Faces drawn per render, 1..6, round-robin (default 6 = the whole cube) — `probe.facesPerFrame`. */
14
+ facesPerFrame?: number;
15
+ /** Render once every N frames drawn (default 1 = every frame) — `probe.everyNth`. */
16
+ everyNth?: number;
17
+ /** Filter the cube for ROUGHNESS (default `true`; `size` a power of two then). Without it the cube has one sharp level:
18
+ * under `scene.environment` only mirror-like surfaces (roughness near 0) are right, and the diffuse ambient stays the
19
+ * scene's own probe's. With it the GPU filters every completed cube into five roughness levels — a brushed or painted
20
+ * body shows its surroundings blurred as much as its roughness says — and the roughest level becomes the diffuse
21
+ * ambient: what the probe sees lights matte surfaces too (a red wall tints them red).
22
+ *
23
+ * COST: one filtering per completed cube (every render at `facesPerFrame` 6, every third at 2), on the frame after:
24
+ * the cube's mip chain plus a pass per level whose taps are the level's texels × `samples` — for a 64 cube at the
25
+ * default 256 samples about 2 million cube taps — and ten small driver submissions on the CPU. `everyNth` /
26
+ * `facesPerFrame` space the filterings out. Fixed. */
27
+ prefilter?: boolean;
28
+ /** The filter's samples per texel, 1..2048 (default 256 — a quarter of Filament's offline default: each sample reads
29
+ * the mip that matches its footprint, which keeps a low count smooth; raise it if a mid-roughness surface reads
30
+ * blotchy). Smoothness for time; read with `prefilter` only. */
31
+ samples?: number;
32
+ /** Near clip plane in metres (default 0.01). Fixed. */
33
+ near?: number;
34
+ /** Far clip plane in metres (default 1000). Fixed. */
35
+ far?: number;
36
+ };
37
+ /**
38
+ * A REFLECTION PROBE: a cube of the scene's light drawn around a point, live — chrome and glass show what is actually
39
+ * around them, moving objects included. A Node: place it, or parent it (`car.add(probe)` — it rides along); the six faces
40
+ * look along the world axes from its WORLD position (its rotation and scale are ignored). It draws once it is in a scene
41
+ * (added to one, or under a node that is), every frame that scene is on show — after the frame's update passes, before
42
+ * the scene's own picture — in linear HDR light (no tone mapping) at the scene camera's exposure. It sees the scene's
43
+ * renderables, lights, environment and sky, not its overlays.
44
+ *
45
+ * Two ways to use it:
46
+ * - `scene.environment = probe`: the scene's reflections come from the cube instead of the scene's own probe
47
+ * (`SceneOptions.ibl`) — and, prefiltered (the default), its diffuse ambient too. A probe in no scene yet starts in
48
+ * that one.
49
+ * - `probe.texture` in a material that samples a cube itself (a custom shader's samplerCubemap): the same cube as a
50
+ * `RenderTexture` — when prefiltered, the filtered one, five roughness levels.
51
+ *
52
+ * COST: a full render is six passes over the scene, plus (prefiltered) a GPU filtering per completed cube (`prefilter`
53
+ * says what one costs). Keep `size` small (128 is a lot), spread the cube over frames (`facesPerFrame: 2` = a whole cube
54
+ * every 3 frames, a third of the cost each), render less often (`everyNth`), leave out what it need not see (`layers` —
55
+ * its carrier first), keep `shadows` off, and turn it off once a static surrounding is drawn (`enabled = false` keeps the
56
+ * last cube).
57
+ *
58
+ * LIFETIME: the cube and its view live as long as the probe — keep a reference (`scene.environment` holds it too).
59
+ * Destroying the probe's node stops the view (the cube keeps its last light); moving it to another scene later does not
60
+ * move the view. Throws on a host without render textures (`createRenderTexture` — the web hosts, for now).
61
+ */
62
+ export declare class ReflectionProbe extends Node {
63
+ /** The cube the probe draws (`cubemap`, linear HDR light) — the filtered one, five roughness levels, when `prefilter`. */
64
+ readonly texture: RenderTexture;
65
+ /** Filtered for roughness (the `prefilter` option). */
66
+ readonly prefilter: boolean;
67
+ private readonly _layers;
68
+ private readonly _shadows;
69
+ private readonly _near;
70
+ private readonly _far;
71
+ private _enabled;
72
+ private _everyNth;
73
+ private _facesPerFrame;
74
+ constructor(options?: ReflectionProbeOptions);
75
+ /** Whether the probe draws (default `true`). Off keeps the last cube and costs nothing — a probe that has drawn its
76
+ * static surroundings once; back on, it renders on the next frame drawn. */
77
+ get enabled(): boolean;
78
+ set enabled(on: boolean);
79
+ /** Render once every N frames DRAWN (default 1 = every frame). The count restarts with each change: the next frame
80
+ * drawn renders, then one in every N. */
81
+ get everyNth(): number;
82
+ set everyNth(n: number);
83
+ /** The cube's faces drawn per render, 1..6 (default 6), round-robin from where the last render stopped: 2 spreads a
84
+ * whole cube over 3 renders at a third of the cost each. A prefiltered cube is filtered each time it completes. */
85
+ get facesPerFrame(): number;
86
+ set facesPerFrame(n: number);
87
+ }
@@ -0,0 +1,29 @@
1
+ import { Texture } from "./Texture";
2
+ /**
3
+ * A texture a render view draws into — made by `camera.renderToTexture({ width, height })` (a 2D picture: the
4
+ * tone-mapped image as the screen would show it) or by a `ReflectionProbe` (`probe.texture`: a cube of linear HDR light).
5
+ * It IS a `Texture`: a 2D one goes wherever an image does (`material.map` — a monitor, a rear-view mirror), a cube
6
+ * wherever a cubemap does (`scene.environment` takes the probe itself).
7
+ *
8
+ * ROW ORDER: a 2D render texture's first texel row is the BOTTOM of the camera's picture (OpenGL's order, on every
9
+ * 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
10
+ * 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.
11
+ *
12
+ * LIFETIME: the texture lives while something holds it — the camera or probe that made it (each keeps its texture), a
13
+ * material showing it — and keeps its last picture when its view stops (`enabled = false`, the camera's next
14
+ * `renderToTexture`, the camera gone).
15
+ */
16
+ export declare class RenderTexture extends Texture {
17
+ /** A cube (a `ReflectionProbe`'s) rather than a 2D picture (a `Camera`'s). A cube's `width` and `height` are both its
18
+ * edge. */
19
+ readonly cubemap: boolean;
20
+ /**
21
+ * RGBA8 bytes (`width × height × 4`) of a rectangle of the texture as the NEXT frame drawn leaves it — a GPU
22
+ * readback, for checks and tools, not a per-frame path. Rows come in the texture's own order: the first is texel row
23
+ * `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
24
+ * picture). A camera's (LDR) texture gives its stored sRGB bytes, a probe's (HDR) its linear light clamped to 0..1 —
25
+ * a prefiltered probe its sharpest level. Rejects on a host without the call, or when the host refuses the read (the
26
+ * rectangle outside the texture, the engine gone first).
27
+ */
28
+ readPixels(x: number, y: number, width: number, height: number, face?: number): Promise<Uint8Array>;
29
+ }