lecodes-sdk 0.20.0 → 0.20.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (64) hide show
  1. package/dist/global.d.ts +31 -0
  2. package/dist/inject.js +361 -260
  3. package/dist/types/audio/Bus.d.ts +45 -0
  4. package/dist/types/audio/Sound.d.ts +28 -0
  5. package/dist/types/audio/Voice.d.ts +27 -0
  6. package/dist/types/audio/audio.d.ts +83 -0
  7. package/dist/types/audio/support.d.ts +1 -0
  8. package/dist/types/gl/AudioSource.d.ts +60 -0
  9. package/dist/types/gl/AudioZone.d.ts +32 -0
  10. package/dist/types/gl/DecalSet.d.ts +103 -0
  11. package/dist/types/gl/Geometry.d.ts +5 -0
  12. package/dist/types/gl/Light.d.ts +7 -0
  13. package/dist/types/gl/Material.d.ts +86 -2
  14. package/dist/types/gl/Mesh.d.ts +11 -0
  15. package/dist/types/gl/Scene.d.ts +23 -0
  16. package/dist/types/gl/SceneAudio.d.ts +11 -0
  17. package/dist/types/gl/Texture.d.ts +29 -1
  18. package/dist/types/gl/animation/AnimationClip.d.ts +25 -12
  19. package/dist/types/gl/animation/core.d.ts +15 -10
  20. package/dist/types/gl/state.d.ts +0 -1
  21. package/dist/types/inject.d.ts +10 -0
  22. package/dist/types/runtime/input.d.ts +11 -0
  23. package/dist/types/ui/UIImage.d.ts +15 -5
  24. package/dist/types.json +1 -1
  25. package/package.json +1 -1
  26. package/prompts/dist/2d-game.md +408 -197
  27. package/prompts/dist/3d-app.md +491 -166
  28. package/prompts/dist/ar-app.md +373 -163
  29. package/prompts/dist/design.md +83 -87
  30. package/prompts/dist/ui-app.md +325 -136
  31. package/src/audio/Bus.ts +102 -0
  32. package/src/audio/Sound.ts +96 -0
  33. package/src/audio/Voice.ts +102 -0
  34. package/src/audio/audio.ts +161 -0
  35. package/src/audio/support.ts +6 -0
  36. package/src/bridges.d.ts +1481 -1352
  37. package/src/compile/compileProject.ts +30 -15
  38. package/src/compile/index.ts +3 -0
  39. package/src/core/Aspect.ts +33 -8
  40. package/src/g2/Scene2D.ts +7 -0
  41. package/src/gl/AudioSource.ts +113 -0
  42. package/src/gl/AudioZone.ts +75 -0
  43. package/src/gl/CameraPlace.ts +52 -52
  44. package/src/gl/DecalSet.ts +233 -0
  45. package/src/gl/Geometry.ts +5 -0
  46. package/src/gl/Light.ts +16 -0
  47. package/src/gl/Lightmap.ts +3 -2
  48. package/src/gl/Material.ts +152 -4
  49. package/src/gl/Mesh.ts +20 -1
  50. package/src/gl/Ragdoll.ts +270 -270
  51. package/src/gl/Scene.ts +41 -7
  52. package/src/gl/SceneAudio.ts +26 -0
  53. package/src/gl/Texture.ts +43 -3
  54. package/src/gl/Trigger.ts +45 -45
  55. package/src/gl/Vehicle.ts +5 -5
  56. package/src/gl/animation/AnimationClip.ts +43 -20
  57. package/src/gl/animation/Animator.ts +4 -3
  58. package/src/gl/animation/core.ts +20 -15
  59. package/src/gl/scenarios.ts +291 -291
  60. package/src/gl/state.ts +1 -1
  61. package/src/inject.ts +12 -0
  62. package/src/runtime/input.ts +6 -1
  63. package/src/scene/gizmos.ts +148 -148
  64. package/src/ui/UIImage.ts +21 -7
@@ -0,0 +1,45 @@
1
+ export type ReverbPreset = "room" | "hall" | "cave" | "arena" | "bathroom" | "outdoor";
2
+ export type ReverbParams = {
3
+ /** 0 (a closet) … 1 (a cathedral). */
4
+ roomSize?: number;
5
+ /** High-frequency loss per reflection, 0 … 1. */
6
+ damping?: number;
7
+ /** Stereo width of the tail, 0 … 1. */
8
+ width?: number;
9
+ /** Wet amount, 0 … 1. */
10
+ mix?: number;
11
+ /** Seconds before the tail starts (a big hall: 0.02 … 0.05). */
12
+ preDelay?: number;
13
+ };
14
+ export type EchoParams = {
15
+ /** Seconds between repeats (up to 2). */
16
+ delay?: number;
17
+ /** Feedback, 0 … 0.95 — how many repeats survive. */
18
+ decay?: number;
19
+ /** Wet amount, 0 … 1. */
20
+ mix?: number;
21
+ };
22
+ export declare const REVERB_PRESETS: Record<ReverbPreset, Required<ReverbParams>>;
23
+ export declare class Bus {
24
+ readonly name: string;
25
+ private _volume;
26
+ private _muted;
27
+ private _reverb;
28
+ private _echo;
29
+ private _lowpass;
30
+ private get _live();
31
+ get volume(): number;
32
+ set volume(v: number);
33
+ get muted(): boolean;
34
+ set muted(v: boolean);
35
+ /** A preset name, explicit params, or null (off). */
36
+ get reverb(): ReverbPreset | ReverbParams | null;
37
+ set reverb(v: ReverbPreset | ReverbParams | null);
38
+ get echo(): EchoParams | null;
39
+ set echo(v: EchoParams | null);
40
+ /** Low-pass cutoff in Hz (20 … 20000), null = off. */
41
+ get lowpass(): number | null;
42
+ set lowpass(v: number | null);
43
+ /** Stop every voice on this bus (fade in seconds). */
44
+ stopAll(fade?: number): void;
45
+ }
@@ -0,0 +1,28 @@
1
+ export type SoundOptions = {
2
+ /** Keep two channels (2D playback of stereo material — music beds, ambiences). Default: the clip
3
+ * is decoded MONO, which is what 3D spatialization needs and what SFX are anyway. */
4
+ stereo?: boolean;
5
+ };
6
+ export declare class Sound {
7
+ /** The urls this clip was loaded from (one per variant). */
8
+ readonly urls: readonly string[];
9
+ private readonly _durations;
10
+ private _channels;
11
+ private _disposed;
12
+ private _last;
13
+ private constructor();
14
+ /** Fetch + decode. An array = variants: `play` picks a random one (never the same twice in a row
15
+ * when there are 3 or more). Rejects with the failing url when a file cannot be decoded. */
16
+ static load(src: string | string[], options?: SoundOptions): Promise<Sound>;
17
+ /** Seconds (the first variant's). 0 for a silent clip. */
18
+ get duration(): number;
19
+ /** Decoded channel count: 1, or 2 with `{ stereo: true }`. */
20
+ get channels(): number;
21
+ /** How many variants this clip carries. */
22
+ get variants(): number;
23
+ /** True when the engine has this clip (false on hosts without audio, or after dispose). */
24
+ get ready(): boolean;
25
+ get disposed(): boolean;
26
+ /** Free the engine's PCM. Voices playing it stop at once. Idempotent. */
27
+ dispose(): void;
28
+ }
@@ -0,0 +1,27 @@
1
+ import { Emitter } from "../core/events";
2
+ export type VoiceEvents = {
3
+ /** The voice is over: the clip ended, `stop` completed, or the pool reused the slot. */
4
+ ended: () => void;
5
+ };
6
+ export declare class Voice extends Emitter<VoiceEvents> {
7
+ private _volume;
8
+ private _pitch;
9
+ private _pan;
10
+ private _over;
11
+ addEventListener<K extends keyof VoiceEvents>(channel: K, callback: VoiceEvents[K]): void;
12
+ /** True while the engine plays this voice. */
13
+ get playing(): boolean;
14
+ /** Seconds into the clip. */
15
+ get time(): number;
16
+ get volume(): number;
17
+ set volume(v: number);
18
+ get pitch(): number;
19
+ set pitch(v: number);
20
+ /** 2D voices only: -1 left … 1 right. */
21
+ get pan(): number;
22
+ set pan(v: number);
23
+ /** Stop now, or fade out over `fade` seconds. `ended` fires from the engine afterwards. */
24
+ stop(options?: {
25
+ fade?: number;
26
+ }): void;
27
+ }
@@ -0,0 +1,83 @@
1
+ import { type Vec3Like } from "../math/vec";
2
+ import { Bus } from "./Bus";
3
+ import type { Sound } from "./Sound";
4
+ import { Voice } from "./Voice";
5
+ export type PlaySoundOptions = {
6
+ /** 0 … 1 (and above, at your own risk). Default 1. */
7
+ volume?: number;
8
+ /** Playback rate, 1 = unchanged. Default 1. */
9
+ pitch?: number;
10
+ loop?: boolean;
11
+ /** Bus name; default: the source's bus, `sfx` for 2D. */
12
+ bus?: string;
13
+ /** Higher survives voice stealing when the pool is full. Default 0. */
14
+ priority?: number;
15
+ /** Fade-in seconds. Default 0. */
16
+ fade?: number;
17
+ /** Seconds into the clip to start from. */
18
+ startAt?: number;
19
+ /** 2D only: -1 left … 1 right. */
20
+ pan?: number;
21
+ };
22
+ export type Rolloff = "none" | "inverse" | "linear" | "exponential";
23
+ /** Distance model of an AudioSource / playAt. */
24
+ export type SpatialOptions = {
25
+ /** Metres of full volume around the source. Default 1. */
26
+ minDistance?: number;
27
+ /** Metres beyond which the source no longer gets quieter. Default 50. */
28
+ maxDistance?: number;
29
+ /** How volume falls between the two: `inverse` (default, physical), `linear`, `exponential`, `none`. */
30
+ rolloff?: Rolloff;
31
+ };
32
+ export declare const ROLLOFF: Record<Rolloff, number>;
33
+ export type AudioStats = {
34
+ voicesPlaying: number;
35
+ voicesMono: number;
36
+ voicesStereo: number;
37
+ /** Voices displaced (or dropped) by a fuller pool since start. */
38
+ stolen: number;
39
+ clips: number;
40
+ clipBytes: number;
41
+ /** Master peak since the previous read, linear (1 = full scale). */
42
+ peak: number;
43
+ sampleRate: number;
44
+ /** The reverb zone the listener is in (0 = none) and how far inside (0 … 1). */
45
+ listenerZone: number;
46
+ zoneBlend: number;
47
+ /** Voices rendered binaurally right now (see `audio.hrtf`). */
48
+ hrtfVoices: number;
49
+ };
50
+ declare class AudioSystem {
51
+ private readonly _buses;
52
+ private _hrtf;
53
+ private _hrtfVoices;
54
+ private _timeScalePitch;
55
+ /** True when this host mixes sound. Everything below is a silent no-op otherwise. */
56
+ get supported(): boolean;
57
+ /** HRTF binaural rendering (MIT KEMAR filters on the CPU): sounds get a real up / down / behind in
58
+ * HEADPHONES. Off by default — on speakers it only smears the image. Applies to the `hrtfVoices`
59
+ * nearest 3D voices; the rest keep plain panning. */
60
+ get hrtf(): boolean;
61
+ set hrtf(v: boolean);
62
+ /** `Time.scale` also pitches the sfx bus: slow motion drops every effect's tone, like a film. Default
63
+ * false — a pause only mutes sfx, the menu click keeps its pitch. */
64
+ get timeScalePitch(): boolean;
65
+ set timeScalePitch(v: boolean);
66
+ /** How many voices get the (CPU-heavier) binaural path. Default 16. */
67
+ get hrtfVoices(): number;
68
+ set hrtfVoices(n: number);
69
+ /** A bus by name — the five built-ins, or an app-defined one created on first use. */
70
+ bus(name: string): Bus;
71
+ get master(): Bus;
72
+ /** 2D playback (UI, stingers, music one-shots). */
73
+ play(sound: Sound, options?: PlaySoundOptions): Voice;
74
+ /** A one-shot at a world position with its own transient source — impacts, ricochets, debris.
75
+ * The source is freed when the voice ends. */
76
+ playAt(sound: Sound, position: Vec3Like, options?: PlaySoundOptions & SpatialOptions): Voice;
77
+ /** Stop every voice on every bus (fade in seconds). */
78
+ stopAll(fade?: number): void;
79
+ /** Engine counters for a debug overlay or a perf log. */
80
+ get stats(): AudioStats;
81
+ }
82
+ export declare const audio: AudioSystem;
83
+ export type { AudioSystem };
@@ -0,0 +1 @@
1
+ export declare const audioSupported: boolean;
@@ -0,0 +1,60 @@
1
+ import { Aspect } from "../core/Aspect";
2
+ import type { FieldMeta } from "../core/fields";
3
+ import { type PlaySoundOptions, type Rolloff } from "../audio/audio";
4
+ import type { Sound } from "../audio/Sound";
5
+ import type { Voice } from "../audio/Voice";
6
+ import type { Node } from "./Node";
7
+ export type AudioCone = {
8
+ /** Degrees of full volume around the node's −Z. */
9
+ inner: number;
10
+ /** Degrees where the volume has fallen to `outerGain`. */
11
+ outer: number;
12
+ /** Volume behind the source, 0 … 1. */
13
+ outerGain?: number;
14
+ };
15
+ export declare class AudioSource extends Aspect<"audio", Node> {
16
+ static readonly aspect = "audio";
17
+ static fields: FieldMeta<AudioSource>;
18
+ private _minDistance;
19
+ private _maxDistance;
20
+ private _rolloff;
21
+ private _cone;
22
+ private _doppler;
23
+ private _spread;
24
+ private _occlusion;
25
+ private _bus;
26
+ /** Metres of full volume around the node. Default 1. */
27
+ get minDistance(): number;
28
+ set minDistance(v: number);
29
+ /** Metres beyond which the source no longer gets quieter. Default 50. */
30
+ get maxDistance(): number;
31
+ set maxDistance(v: number);
32
+ /** `inverse` (default), `linear`, `exponential`, `none`. */
33
+ get rolloff(): Rolloff;
34
+ set rolloff(v: Rolloff);
35
+ /** Directional source along the node's −Z; null = omnidirectional (default). */
36
+ get cone(): AudioCone | null;
37
+ set cone(v: AudioCone | null);
38
+ /** Doppler amount 0 … 1 (0 = off, the default) — needs the node to actually move. */
39
+ get doppler(): number;
40
+ set doppler(v: number);
41
+ /** 0 = pin-point panning (default) … 1 = the same on every speaker (a big, close source). */
42
+ get spread(): number;
43
+ set spread(v: number);
44
+ /** The engine raycasts listener → source (against solid bodies) and muffles the voices when
45
+ * something is in the way. Default false. */
46
+ get occlusion(): boolean;
47
+ set occlusion(v: boolean);
48
+ /** Default bus for voices on this source. Default `sfx`. */
49
+ get bus(): string;
50
+ set bus(v: string);
51
+ /** Live voices on this source. */
52
+ get voices(): number;
53
+ onAttach(): void;
54
+ onDetach(): void;
55
+ /** Play a clip from this node. */
56
+ play(sound: Sound, options?: PlaySoundOptions): Voice;
57
+ /** Stop every voice on this source (fade in seconds). */
58
+ stopAll(fade?: number): void;
59
+ private _push;
60
+ }
@@ -0,0 +1,32 @@
1
+ import { Aspect } from "../core/Aspect";
2
+ import { type ReverbParams, type ReverbPreset } from "../audio/Bus";
3
+ import { type Vec3Like } from "../math/vec";
4
+ import type { Node } from "./Node";
5
+ export declare class AudioZone extends Aspect<"audioZone", Node> {
6
+ static readonly aspect = "audioZone";
7
+ private _box;
8
+ private _sphere;
9
+ private _reverb;
10
+ private _blend;
11
+ private _bus;
12
+ /** The engine's zone id — what `audio.stats.listenerZone` reports while the listener is inside. */
13
+ get id(): number;
14
+ /** Half-extents [hx, hy, hz]; falls back to the node's Shape box, then a 1 m cube. */
15
+ get box(): Vec3Like | null;
16
+ set box(v: Vec3Like | null);
17
+ /** Radius; falls back to the node's Shape sphere. */
18
+ get sphere(): number | null;
19
+ set sphere(v: number | null);
20
+ /** A preset name or explicit params (see Bus.reverb). */
21
+ get reverb(): ReverbPreset | ReverbParams;
22
+ set reverb(v: ReverbPreset | ReverbParams);
23
+ /** Crossfade depth in metres inside the border (0 = a hard edge). Default 1. */
24
+ get blend(): number;
25
+ set blend(v: number);
26
+ /** The bus the zone's reverb rides on. Default `sfx`. */
27
+ get bus(): string;
28
+ set bus(v: string);
29
+ onAttach(): void;
30
+ onDetach(): void;
31
+ private _push;
32
+ }
@@ -0,0 +1,103 @@
1
+ import { Node } from "./Node";
2
+ import { Material } from "./Material";
3
+ import type { Texture } from "./Texture";
4
+ import { type Vec3Like } from "../math/vec";
5
+ import { type QuatLike } from "../math/quat";
6
+ import { type ColorInput } from "../core/color";
7
+ /** Per-decal look and life — defaults come from the set's options. */
8
+ export type DecalOptions = {
9
+ /** Image width and height on the surface (world units); a number = square. Default 0.2. */
10
+ size?: number | [number, number];
11
+ /** Projection depth (world units): how far in front of and behind the hit point the decal still
12
+ * lands. Default = the smaller of width / height. */
13
+ depth?: number;
14
+ /** Atlas cell (row-major from the top-left) for a set with a `sheet`; `"random"` picks one.
15
+ * Default 0. */
16
+ frame?: number | "random";
17
+ /** Tint multiplied into the image. Default white. */
18
+ tint?: ColorInput;
19
+ /** 0..1 on top of the tint's alpha. Default 1. */
20
+ opacity?: number;
21
+ /** Seconds until the decal is gone; 0 = stays until recycled. Default 0. */
22
+ life?: number;
23
+ /** Seconds to fade in after spawning. Default 0. */
24
+ fadeIn?: number;
25
+ /** Seconds of fade at the end of `life` (ignored with life 0). Default 0. */
26
+ fadeOut?: number;
27
+ };
28
+ export type DecalSetOptions = DecalOptions & {
29
+ /** The material — `Material.decal({ map })` by default (`map` below is its shortcut). A custom
30
+ * material must keep decal.mat's vertex contract. */
31
+ material?: Material;
32
+ /** Atlas texture for the default material. */
33
+ map?: Texture;
34
+ /** Tangent-space normal atlas (same cell grid) → a RELIEF decal that bends the surface's
35
+ * lighting instead of painting a colour (`Material.decal` `normalMap`). Footprints and dents
36
+ * need only this; a bullet hole gives `map` too and its colour multiplies in. */
37
+ normalMap?: Texture;
38
+ /** Relief strength for `normalMap`. Default 1. Default material only. */
39
+ bump?: number;
40
+ /** Atlas grid of the map: columns, or [columns, rows]. Default 1 (the whole texture). */
41
+ sheet?: number | [number, number];
42
+ /** Slot budget — the most decals alive at once. Default 256. */
43
+ max?: number;
44
+ /** Soft fraction (0..1) of the box's half depth at both ends, so an oblique surface leaves the
45
+ * box gently. Default 0.3. Default material only. */
46
+ edge?: number;
47
+ /** Surfaces turned more than this away from the projection axis fade out — the cosine of the
48
+ * angle (0.3 ≈ 72°) keeps a floor hit off the wall it meets; 0 = project onto anything.
49
+ * Default 0.3. Default material only. */
50
+ angleFade?: number;
51
+ /** HDR boost of the image (0 = none). Default material only. */
52
+ emissive?: number;
53
+ name?: string;
54
+ /** Coarse draw order, 0 (first) … 7 (last); default 4 — see `Mesh.renderPriority`. Decals are
55
+ * blended and sort with the other blended draws of their priority. */
56
+ renderPriority?: number;
57
+ };
58
+ /** `spawn` placement: where the image sits on the surface. */
59
+ export type DecalSpawnOptions = DecalOptions & {
60
+ /** World direction the image's top points along the surface (projected onto it) — a footprint's
61
+ * travel direction. Default: a random spin. */
62
+ up?: Vec3Like;
63
+ /** Extra spin around the normal, radians. Default: random when `up` is not given, else 0. */
64
+ rotation?: number;
65
+ };
66
+ /** `place` / `update` placement: a full frame, like a node looking INTO the surface (its −Z is the
67
+ * projection direction, +Y the image's top). */
68
+ export type DecalPlacement = DecalOptions & {
69
+ position: Vec3Like;
70
+ /** A quaternion, or Euler degrees (YXZ) like `Node.eulerAngles`. Default identity = projecting
71
+ * down −Z. */
72
+ rotation?: QuatLike | Vec3Like;
73
+ };
74
+ export declare class DecalSet extends Node {
75
+ private _material;
76
+ private readonly _max;
77
+ private readonly _cols;
78
+ private readonly _rows;
79
+ private readonly _defaults;
80
+ private readonly _rec;
81
+ private readonly _slots;
82
+ private static _warned;
83
+ constructor(options?: DecalSetOptions);
84
+ get material(): Material;
85
+ /** Decals alive right now. */
86
+ get count(): number;
87
+ /** The slot budget the set was created with. */
88
+ get max(): number;
89
+ /** Coarse draw order, 0 … 7 — see `Mesh.renderPriority`. Write-only. */
90
+ set renderPriority(v: number);
91
+ /** Stamp a decal on a surface: `point` on it, `normal` out of it (a raycast hit, a foot plant
92
+ * with `Vec3.up`). Returns the slot for `update` / `remove` (−1 when the host draws none). */
93
+ spawn(point: Vec3Like, normal: Vec3Like, options?: DecalSpawnOptions): number;
94
+ /** Place a decal by a full frame (an editor-placed stain, a moving marker). Returns the slot. */
95
+ place(placement: DecalPlacement): number;
96
+ /** Move / restyle a placed decal; fields left out keep the values it was placed with. Its birth
97
+ * time (the life clock) is kept. */
98
+ update(slot: number, placement: Partial<DecalPlacement>): this;
99
+ remove(slot: number): this;
100
+ clear(): this;
101
+ private _writePlacement;
102
+ private _write;
103
+ }
@@ -10,6 +10,11 @@ export declare class Geometry {
10
10
  * tiles per face, which would fold every face onto the same texels); absent = the host reuses
11
11
  * `uv`, which is right for a plane. */
12
12
  uv1?: Float32Array;
13
+ /** Per-vertex COLOURS: 4 bytes (r, g, b, a) per vertex, 0..255. A material that declares
14
+ * `requires: [color]` reads them through `getColor()` — one mesh, many colours, no material per
15
+ * shade (a debug drawer, a gradient along a curve). Absent = every vertex white, which is what
16
+ * the built-in materials expect. */
17
+ colors?: Uint8Array;
13
18
  constructor(vertices: Float32Array, normals: Float32Array, indices: Uint16Array, uv: Float32Array);
14
19
  translate(x: number, y: number, z: number): this;
15
20
  scale(x: number, y: number, z: number): this;
@@ -53,6 +53,7 @@ export declare class Light extends Node {
53
53
  private _intensity;
54
54
  /** The most recently created sun — what `Lightmap.load` uses unless told otherwise. */
55
55
  static lastSun: Light | null;
56
+ _shadowDistance: number;
56
57
  /** A directional sun light. */
57
58
  static sun(options?: SunOptions): Light;
58
59
  /**
@@ -70,4 +71,10 @@ export declare class Light extends Node {
70
71
  set intensity(value: number);
71
72
  destroy(): void;
72
73
  set color(value: ColorInput);
74
+ get color(): ColorInput;
75
+ /** Sun direction as created (the engine keeps it; a settings menu rebuilds a sun from it). */
76
+ get direction(): [number, number, number];
77
+ /** Sun shadow options as created — creation-time in the engine, so a change means a new sun. */
78
+ get shadowsQuality(): number;
79
+ get shadowDistance(): number;
73
80
  }
@@ -7,8 +7,47 @@ export type MaterialColorOptions = {
7
7
  color?: ColorInput;
8
8
  map?: Texture | Canvas | null;
9
9
  };
10
+ /** How a stencil test / write on a material compares and what it writes. `test` runs against the
11
+ * scene's stencil buffer (`Scene.stencil` must be on); the ops say what the buffer gets when the
12
+ * fragment passes / fails the stencil test / fails the depth test (`replace` writes `ref`). */
13
+ export type StencilTest = "always" | "never" | "less" | "lessEqual" | "greater" | "greaterEqual" | "equal" | "notEqual";
14
+ export type StencilOp = "keep" | "zero" | "replace" | "increment" | "decrement" | "invert";
15
+ export type MaterialStencil = {
16
+ /** Write the stencil buffer at all. Default `false`. */
17
+ write?: boolean;
18
+ /** The reference value, 0..255. Default 0. */
19
+ ref?: number;
20
+ /** Default `"always"`. */
21
+ test?: StencilTest;
22
+ onPass?: StencilOp;
23
+ onFail?: StencilOp;
24
+ onDepthFail?: StencilOp;
25
+ readMask?: number;
26
+ writeMask?: number;
27
+ };
28
+ /** Render state a material instance can override at run time (Filament keeps the defaults in the
29
+ * shader package; these are per-instance overrides on top). */
30
+ export type MaterialStateOptions = {
31
+ /** Test against the scene's depth buffer. `false` draws over everything already drawn — an
32
+ * editor gizmo, a marker that must never hide behind a wall. Pair it with a high
33
+ * `Mesh.renderPriority` so nothing drawn later covers it. Default `true`. */
34
+ depthTest?: boolean;
35
+ /** Write to the depth buffer. Leave it on for something drawn over the scene whose own parts must
36
+ * still occlude each other (a gizmo's cone in front of its shaft). Unset = the shader's default
37
+ * (on for opaque, off for a blended one). */
38
+ depthWrite?: boolean;
39
+ /** Draw both faces (no back-face culling): a plane seen from behind, a ribbon, cloth, a flat
40
+ * marker. Default `false` = the shader's culling (back faces dropped). */
41
+ doubleSided?: boolean;
42
+ /** A stencil test / write for this material — see `MaterialStencil`. The selection-outline
43
+ * recipe: the object writes `{ write: true, ref: 1, onPass: "replace" }`, and a slightly larger
44
+ * copy of it draws with `{ test: "notEqual", ref: 1 }` + `depthTest: false` in `renderPriority` 7. */
45
+ stencil?: MaterialStencil;
46
+ };
47
+ /** @deprecated the name before doubleSided / stencil joined it — the same type */
48
+ export type MaterialDepthOptions = MaterialStateOptions;
10
49
  /** `Material.unlit` options. */
11
- export type UnlitMaterialOptions = MaterialColorOptions & {
50
+ export type UnlitMaterialOptions = MaterialColorOptions & MaterialStateOptions & {
12
51
  /** Alpha-blend this material instead of drawing it opaque. Opaque is the default: a blended draw
13
52
  * writes no depth, is sorted back-to-front and casts no shadow, which is rarely what a flat
14
53
  * colour wants. Turn it on for anything that must show what is behind it — a glass pane, a
@@ -16,6 +55,26 @@ export type UnlitMaterialOptions = MaterialColorOptions & {
16
55
  * (`"#ffffff80"`) mean anything; the opaque material has no alpha channel at all. */
17
56
  transparent?: boolean;
18
57
  };
58
+ /** `Material.decal` options — the projected-decal material a `DecalSet` draws with. */
59
+ export type DecalMaterialOptions = {
60
+ /** The atlas (a `DecalSet`'s `sheet` cuts it into cells). */
61
+ map?: Texture;
62
+ /** A tangent-space normal atlas (same cell grid; +X = the image's right, +Y = its top; LINEAR —
63
+ * `Texture.fromPixels(…, { srgb: false })`). Turns the decal into a RELIEF decal: instead of
64
+ * painting a colour it bends the surface's lighting, so a footprint or a dent shows on any
65
+ * surface without a colour of its own. With `map` too, the colour multiplies in (a crater
66
+ * darkens by the map's alpha). The sun term applies in shadow as well (no shadow read). */
67
+ normalMap?: Texture;
68
+ /** Relief strength — the normal map's xy scale. Default 1. */
69
+ bump?: number;
70
+ /** Soft fraction (0..1) of the box's half depth at both ends. Default 0.3. */
71
+ edge?: number;
72
+ /** Cosine of the surface angle past which the decal fades (0.3 ≈ 72°); 0 = project onto
73
+ * anything. Default 0.3. */
74
+ angleFade?: number;
75
+ /** HDR boost of the image (0 = none). */
76
+ emissive?: number;
77
+ };
19
78
  /** `Material.particles` options — the default point-sprite material for particle systems. */
20
79
  export type ParticlesMaterialOptions = {
21
80
  /** Sprite texture — a single image or a flipbook sheet of frames. Unset = soft round dot. */
@@ -54,12 +113,14 @@ export type ParticlesMaterialOptions = {
54
113
  stretch?: number;
55
114
  };
56
115
  /** `Material.lit` options — PBR scalars on top of the color/map pair. */
57
- export type LitMaterialOptions = MaterialColorOptions & {
116
+ export type LitMaterialOptions = MaterialColorOptions & MaterialStateOptions & {
58
117
  /** Perceptual roughness, 0 (mirror) … 1 (matte). Unset = the shader's default. */
59
118
  roughness?: number;
60
119
  /** Metallic factor, 0 (dielectric) … 1 (metal). Unset = the shader's default. */
61
120
  metallic?: number;
62
121
  };
122
+ /** `Material.lightmapShading` tiers — see the setter. */
123
+ export type LightmapShading = "full" | "baked" | "baked-lite";
63
124
  export declare class Material {
64
125
  readonly shader: FetchResponse | "unknown";
65
126
  readonly uniforms: Record<string, UniformValue>;
@@ -68,6 +129,15 @@ export declare class Material {
68
129
  /** Set a uniform (chainable). */
69
130
  set(key: string, value: UniformValue): this;
70
131
  set color(c: ColorInput);
132
+ /** Depth test against the scene (write-only; see `MaterialDepthOptions`). A host that predates
133
+ * the call leaves the material as the shader has it. */
134
+ set depthTest(on: boolean);
135
+ /** Depth write (write-only; see `MaterialStateOptions`). */
136
+ set depthWrite(on: boolean);
137
+ /** Both faces drawn (write-only; see `MaterialStateOptions`). */
138
+ set doubleSided(on: boolean);
139
+ /** The stencil test / write (write-only; `null` = back to none). See `MaterialStencil`. */
140
+ set stencil(s: MaterialStencil | null);
71
141
  set map(value: Texture | Canvas | null);
72
142
  /** PBR lit material. */
73
143
  static lit(options?: LitMaterialOptions): Material;
@@ -77,6 +147,10 @@ export declare class Material {
77
147
  * driven by the particle curves. Uniforms all default to 0 on a fresh instance, so every look
78
148
  * knob is primed here; JS writes after construction override them. */
79
149
  static particles(options?: ParticlesMaterialOptions): Material;
150
+ /** Projected-decal material (`DecalSet`): samples the atlas where the decal's box meets the opaque
151
+ * scene behind it. Blended, no depth write, no shadows — the engine keeps the scene depth bound
152
+ * while a set with live decals is on screen. */
153
+ static decal(options?: DecalMaterialOptions): Material;
80
154
  /** Material that samples a VideoPlayer's texture. */
81
155
  static video(map?: Texture): Material;
82
156
  /** The lightmap material (docs/lightmap-plan.md): PBR base colour × a baked shadow/AO atlas on UV1.
@@ -92,6 +166,16 @@ export declare class Material {
92
166
  * unset layer is white and an unbaked terrain is fully lit. */
93
167
  static terrain(): Material;
94
168
  private static _lmTemplate;
169
+ static _lightmapShading: LightmapShading;
170
+ /** Which shader lightmapped models take — a graphics-quality tier, engine-wide. `"full"` is filament's
171
+ * lit path over the baked atlas (IBL specular, real-time point lights such as a muzzle flash, sun
172
+ * shadows on dynamic objects). `"baked"` keeps the baked light and an approximated ambient but drops
173
+ * the lit path: ~40 % cheaper per pixel on a fill-bound GPU. `"baked-lite"` is that minus the normal,
174
+ * metallic/roughness and occlusion map reads (the factors stand in, the atlas keeps the baked AO;
175
+ * emissive still glows): +20 % more at 1080p on the same GPU. Read when a lightmapped model LOADS, so set
176
+ * it before the level (a settings menu applies it on the next level load), like `Texture.maxSize`. */
177
+ static get lightmapShading(): LightmapShading;
178
+ static set lightmapShading(mode: LightmapShading);
95
179
  /** Shadow-catcher material (transparent except where shadows fall). */
96
180
  static shadow(color?: ColorInput): Material;
97
181
  /** Load a custom compiled shader (.mat URL) as a material. */
@@ -11,15 +11,26 @@ export type MeshOptions = {
11
11
  name?: string;
12
12
  castShadows?: boolean;
13
13
  receiveShadows?: boolean;
14
+ /** Coarse draw order, 0 (first) … 7 (last); default 4. See `Mesh.renderPriority`. */
15
+ renderPriority?: number;
14
16
  };
15
17
  export declare class Mesh extends Node {
16
18
  private _geometry?;
17
19
  constructor(geometry?: Geometry, material?: Material);
18
20
  get geometry(): Geometry | undefined;
21
+ /** Replace the geometry in place — the node, its transform and its material stay, the vertex and
22
+ * index buffers are rebuilt. What an editor overlay or a debug drawer redraws with. */
23
+ setGeometry(geometry: Geometry): this;
19
24
  /** A Mesh always carries a material (slot 0) — see Node.setMaterial for the slot API. */
20
25
  get material(): Material;
21
26
  set material(m: Material);
22
27
  set culling(v: boolean);
28
+ /** Coarse draw order within the frame: 0 draws first, 7 last, 4 is the default (Filament's
29
+ * renderable priority; within one priority opaque draws sort front-to-back, blended back-to-front).
30
+ * Something drawn over the scene with `Material.depthTest = false` goes in 7, so nothing drawn
31
+ * after it can cover it. Write-only; a host that predates the call ignores it. */
32
+ set renderPriority(v: number);
33
+ private static _warnedPriority;
23
34
  set castShadows(v: boolean);
24
35
  set receiveShadows(v: boolean);
25
36
  static box(options?: MeshOptions & {
@@ -4,6 +4,7 @@ import { Presentable, type PresentOptions } from "../ui/presentable";
4
4
  import type { ClickEvent, TouchStartEvent } from "../runtime/touch";
5
5
  import type { FetchResponse } from "../runtime/fetch";
6
6
  import { Camera } from "./Camera";
7
+ import { SceneAudio } from "./SceneAudio";
7
8
  import { type ControlsHandle, type ControlsOptions } from "./controls";
8
9
  import { Material } from "./Material";
9
10
  import { Node } from "./Node";
@@ -95,6 +96,9 @@ export type SceneOptions = {
95
96
  * takes over). Unset keeps the host default (desktop 4×, mobile/web off). The biggest single
96
97
  * fill-rate cost after resolution — turn it down on big screens before anything else. */
97
98
  antialias?: boolean | 2 | 4;
99
+ /** Keep a stencil buffer for this scene (off by default: it costs memory and a clear per frame).
100
+ * Needed before any material's `stencil` test or write does anything. */
101
+ stencil?: boolean;
98
102
  /** Render the 3D at this fraction of the viewport (0.25–1) and upscale; the UI stays at native
99
103
  * resolution. A fixed, predictable cut of per-pixel GPU work — `0.75` is ~45 % cheaper and
100
104
  * barely visible in motion, `0.5` quarters it. Headless renders ignore it. */
@@ -118,6 +122,10 @@ export type SceneOptions = {
118
122
  * scene file — `env` is applied before the nodes build) and leaves already-loaded ones alone.
119
123
  * On the desktop host `CREATOR_TEXTURE_ANISOTROPY` overrides it, for tuning without a rebuild. */
120
124
  anisotropy?: number;
125
+ /** Engine-wide cap on texture size, 0 / unset = none (see `Texture.maxSize`): a KTX2 above it
126
+ * loses its top mip levels on load, a glTF image is downsampled. Applied before this scene's
127
+ * assets load; like `anisotropy` it does not touch textures already loaded. */
128
+ maxTextureSize?: number;
121
129
  /** The look on an HDR display (a screen with headroom above SDR white — Apple XDR panels, the
122
130
  * macOS host today); ignored on SDR. `strength` 0..1 is how much of the picture reaches for the
123
131
  * display's headroom (0 only what SDR clipped, 1 nearly everything; default 0.35). `paperWhite`
@@ -132,6 +140,8 @@ export type SceneOptions = {
132
140
  };
133
141
  export declare class Scene implements Presentable {
134
142
  readonly camera: Camera;
143
+ /** The listener + global 3D audio knobs (docs/audio-plan.md). */
144
+ readonly audio: SceneAudio;
135
145
  readonly _touchStartListeners: Array<(ev: TouchStartEvent<Node | null>) => void>;
136
146
  private _material?;
137
147
  private static _active;
@@ -153,6 +163,19 @@ export declare class Scene implements Presentable {
153
163
  setFog(options: FogOptions | false): void;
154
164
  setMaterialGlobalParameter(i: number, x: number, y: number, z: number, w: number): void;
155
165
  setAntialias(enabled: boolean, scale?: number): void;
166
+ /** Depth-reading effects on / off (a graphics-settings menu): soft particles and projected decals
167
+ * read the scene depth, which costs a depth pre-pass of every opaque draw (~12 % of a fill-bound
168
+ * frame). Off = hard-edged particles, no decals, no pre-pass. Engine-wide, live. */
169
+ setDepthEffects(enabled: boolean): void;
170
+ /** LOD distance (a graphics-settings menu): the engine's LOD thresholds × `bias`. 2 = every level
171
+ * switches at half the distance (a model must look twice as big on screen to keep its detail),
172
+ * 0.5 = full detail twice as far, 1 = the defaults. Engine-wide, live. Only GLBs that carry
173
+ * `_LOD<n>` meshes (`lecodes assets doctor --lod`) have levels to switch. */
174
+ setLodBias(bias: number): void;
175
+ /** Runtime form of `bloom` / `bloomIntensity` (a graphics-settings menu). */
176
+ setBloom(enabled: boolean, intensity?: number): void;
177
+ /** The scene's stencil buffer on / off (see `SceneOptions.stencil`). */
178
+ setStencil(enabled: boolean): void;
156
179
  add(...nodes: Node[]): this;
157
180
  remove(...nodes: Node[]): this;
158
181
  /** Attach (and configure) a system, or reconfigure it if already present. Returns the scene typed
@@ -0,0 +1,11 @@
1
+ import type { Node } from "./Node";
2
+ export declare class SceneAudio {
3
+ private _listener;
4
+ private _doppler;
5
+ /** The node the engine listens from; null (default) = the active camera. */
6
+ get listener(): Node | null;
7
+ set listener(node: Node | null);
8
+ /** Multiplies every source's doppler amount (0 = off everywhere). Default 1. */
9
+ get dopplerFactor(): number;
10
+ set dopplerFactor(v: number);
11
+ }